When I open an entry of "Placerating" in Admin, and try to save it after making a change to any field, Django admin displays "This field is required" above the field "Pic".
class Placerating(models.Model):
theplace = models.ForeignKey('ThePlace', on_delete=models.CASCADE, null=True, related_name='placeratings')
pic = models.OneToOneField('fileupload.Picture', on_delete=models.SET_NULL, null=True)
def __unicode__(self):
return self.theplace.name
class Picture(models.Model):
def set_upload_to_info(self, path, name):
self.upload_to_info = (path, name)
file = ImageField(max_length=500, upload_to=user_directory_path)
def filename(self):
return os.path.basename(self.file.name)
theplace = models.ForeignKey(ThePlace, null=True, blank=True, related_name='pictures')
def __unicode__(self):
return str(self.id)
def save(self, *args, **kwargs):
super(Picture, self).save(*args, **kwargs)
I created the entry without problem with a form, so I don't understand why admin would now require this field to be completed.
From the docs about the null argument to model fields:
For both string-based and non-string-based fields, you will also need
to set blank=True if you wish to permit empty values in forms, as the
null parameter only affects database storage (see blank).
The admin uses forms for creation and editing, so you need blank=True in the fields you want to be able to leave blank in the admin.
Related
I have a ReportModel in my django app with 2 field create_user (represents the user that created the report) and write_user (represents the user that last modified the report). I want to automatically save that two fields according to the user that is logged in on django admin site . How do I do that?
Here is the definition of the model
class ReportModel(models.Model):
name = models.CharField(verbose_name=_("Nombre"), max_length=50, blank=False, null=False)
location = models.PointField(verbose_name=_("LocalizaciĆ³n"), srid=4326, blank=False, null=False)
report_type_id = models.ForeignKey("ReportTypeModel", verbose_name=_("Tipo"),
blank=True, null=True, on_delete=models.SET_NULL,
related_name="reports")
start_date = models.DateField(verbose_name=_("Fecha inicio"))
end_date = models.DateField(verbose_name=_("Fecha fin"))
create_user = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='+', verbose_name=_('Creado por'), editable=False, null=True, blank=True)
write_user = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='+', verbose_name=_('Modificado por'), editable=False, null=True, blank=True)
def __str__(self):
return self.name
you can override the create and update methods in your serializer. In the methods before you call the super class update and create methods, you can add the fiels by your self from the request.user
something like
def create(self, validated_data):
"""
Overriding the default create method of the Model serializer.
:param validated_data: data containing all the details of your model
:return: returns a successfully created record
"""
validated_data.update({"create_user": request.user})
# call super class create method here by passing the modified validated_data
return student
In order to capture/record the user who performed a create/update on a model in the django admin, override the save_model method on the admin view.
In the ReportModel admin view declared in admin.py file, override the save_model method as follows:
from models import ReportModel
from django.contrib import admin
#admin.register(ReportModel)
class ReportModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not obj.pk:
obj.create_user = request.user #create_user should only be set once
obj.write_user = request.user #write_user can be set at all times
super().save_model(request, obj, form, change)
Reference: How to associate model with current user while saving
I have a user profile model with optional avatar that looks like
#models.py
class UserProfile(models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
.
.
.
Then a form like:
#forms.py
class UserProfileForm(forms.ModelForm):
avatar = forms.ImageField(required=False,....)
class Meta:
model = UserProfile
Finally, a view including
#views.py
def edit_profile(....)
profile_obj = request.user.userprofile
form = UserProfile(data=request.POST, files=request.FILES, instance=profile_obj)
if form.is_valid():
form.save()
Now when the template for editing the user's profile, and avatar, is rendered, it included a Clear checkbox, which when selected let's the user remove their avatar photo. It leaves myuser.avatar in the state <ImageFieldFile: None>, however it does not delete the file in the storage area of the site itself (i.e. the jpg,png or whatever). I've read that this is by design in Django 1.6, which is all well and good, but how do I override this feature, so that the file is indeed deleted to?
From the shell there exists no problem:
from myapp.models import UserProfile
user1==UserProfile.objects.all()[0]
user1.avatar.delete()
Removes the jpeg too.
EDIT:
I tried using a signal like:
#models.py
.
.
.
#receiver(post_delete, sender=UserProfile)
def avatar_post_delete_handler(sender, **kwargs):
print 'DEBUG: avatar delete triggered'
avatar = kwargs['instance']
storage, path = avatar.original_image.storage, avatar.original_image.path
storage.delete(path)
but this did not even trigger, I guess because I'm not deleting the UserProfile object
in its entirety when the user selects the clear checkbox, but rather just the avatar.
Extend the ImageField like this and use it instead:
class ImageField(models.ImageField):
def save_form_data(self, instance, data):
if data is not None:
file = getattr(instance, self.attname)
if file != data:
file.delete(save=False)
super(ImageField, self).save_form_data(instance, data)
This will delete the old file if you replace it with the new one, or mark it to clear.
Here is the explanation why.
Edit:
There is also an app django-smartfields that includes this functionality plus more, like automatic re-sizing, automatic conversion of images and videos, etc. It achieves it in a more complicated way though, using field descriptors and model customization. But it is very simple to use:
from smartfields import fields
class UserProfile(models.Model):
avatar = fields.ImageField(upload_to='avatars', blank=True)
It will also remove files whenever:
field value was replaced with a new one (either uploaded or set manually)
the model instance itself containing the field is deleted.
Not the best solution at all, but one hackish way might be to store the file for each user under just their username, then on catching a pre_save signal with an empty instance.avatar.name and where the user file exists on disk in the expected place, delete it. Yuck.
class UserProfile(models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
and
#save the avatar for each user as their username
def update_filename(instance, filename):
path = "avatars"
format = instance.user.username
return os.path.join(path, format)
and
#if current instance has empty avatar.name
#and the file exists on disk where expected for user
#deduce user has clicked clear, delete file for user.
#receiver(pre_save, sender=UserProfile)
def avatar_pre_save_handler(sender, instance, **kwargs):
avatar_filepath = settings.MEDIA_ROOT +'/avatars/'+ instance.user.username
if not instance.avatar.name and os.path.isfile(avatar_filepath):
os.remove(avatar_filepath)
One more possible way (that doesn't require special naming of files) is overriding form save method, then calling it with the old avatar as kwarg:
#forms.py
class UserProfileForm(forms.ModelForm):
def save(self, commit=True, *args, **kwargs):
instance = super(UserProfileForm, self).save(commit=False)
old_avatar_name = kwargs.pop('old_avatar_name', None)
new_avatar_name = None
if self.cleaned_data['avatar']:
new_avatar_name = self.cleaned_data['avatar'].name
if old_avatar_name != new_avatar_name:
old_avatar_filepath = settings.MEDIA_ROOT +'/'+ old_avatar_name
if os.path.isfile(old_avatar_filepath):
os.remove(old_avatar_filepath)
if commit:
instance.save()
return instance
Then in the view:
def edit_profile(request,....):
.
.
.
try:
profile_obj = request.user.userprofile
except ObjectDoesNotExist:
return HttpResponseRedirect(reverse('profiles_create_profile'))
if profile_obj.avatar.name:
avatar_kwargs={'old_avatar_name': profile_obj.avatar.name}
else:
avatar_kwargs={}
.
.
.
if form.is_valid():
form.save(**avatar_kwargs)
Use Mixin like below
class ImageDeleteMixin(object):
def delete(self, *args, **kwargs):
if self.avatar:
storage, path = self.avatar.storage, self.avatar.path
super(ImageDeleteMixin, self).delete(*args, **kwargs)
storage.delete(path)
else:
super(ImageDeleteMixin, self).delete(*args, **kwargs)
def save(self, *args, **kwargs):
if self.id:
old_instance = self.__class__._default_manager.get(pk=self.pk)
if (
old_instance.avatar != self.avatar and old_instance.avatar and
old_instance.avatar.path
):
storage, path = old_instance.avatar.storage, old_instance.avatar.path
super(ImageDeleteMixin, self).save(*args, **kwargs)
storage.delete(path)
return
return super(ImageDeleteMixin, self).save(*args, **kwargs)
class UserProfile(ImageDeleteMixin, models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
Depending on your use case, you could hook into Django's Singals:
https://docs.djangoproject.com/en/dev/ref/signals/#post-delete
Or, in a view, when the "checked" variable is sent back to the server, delete the avatar!
I have a model (Application) tied to a foreign_key (Person) table. I was having trouble getting the Person-dropdown in the admin to sort by name instead of by key, and found this Reorder users in django auth as a solution. However, this made the fields mandatory and I can't figure out how to get them to stay optional.
app/models.py
class Person(models.Model):
Full_Name = models.CharField(max_length=200)
def __unicode__(self):
return self.Full_Name
class Application(models.Model):
Name = models.CharField(max_length=100)
Primary_Contact = models.ForeignKey(Person,blank=True,null=True,related_name='appprimarycontact')
def __unicode__(self):
return self.Name
admin.py
class OwnerAdminForm(forms.ModelForm):
Primary_Contact = forms.ModelChoiceField(queryset=Person.objects.order_by('Full_Name'),)
class Meta:
model = Application
class ApplicationAdmin(admin.ModelAdmin):
form = OwnerAdminForm
list_display = ('Name','Primary Contact')
Just add the required=False option on the form field
forms.ModelChoiceField(required=False, queryset=Person.objects.order_by('Full_Name'))
The thing is, if you override the default form widget that django's ModelForm would provide, you would have to explicitly specify required=False, since the default value is True
Banging head against the wall again.
I'm trying to add tags using other known fields
# models.py
class MyModel(models.Model):
...
tags = models.ManyToManyField(Tag, blank=True)
field_m2m = models.ManyToManyField('M2mModel', blank=True)
field_fk = models.ForeignKey('FkModel', blank=True, null=True)
...
def save(self, *args, **kwargs):
for inst in self.field_m2m.all():
self.tags.add(Tag.objects.get(name=inst.name))
self.tags.add(Tag.objects.get(name=self.field_fk.name))
super(MyModel, self).save(*args, **kwargs)
class FkModel(models.Model):
name = models.CharField(max_length=255, unique=True)
...
class M2mModel(models.Model):
name = models.CharField(max_length=255, unique=True)
...
I am 100% sure my field_m2m and field_fk aren't empty and what's not less important: there are instances corresponding to EXISTING tags. I have other functions covering this part well. I have also tried hardcoding the strings (Tag.objects.get(name="mystring")) to be 101% sure.
Yet, no tags are assigned through admin panel.
I tried to go through the steps in shell and it works there.
>>> m = MyModel.objects.get(name='something')
>>> t = Tag.objects.get(name='sometag')
>>> m.tags.add(t)
>>> m.tags.all()
[<Tag: sometag>]
How to make it work from save() method?
Also until the the model instance is created for the first time, traceback is complaining about: "<MyModel: Something>" needs to have a value for field "mymodel" before this many-to-many relationship can be used.
I guess I should save the model instance before even doing aforementioned assignments, right? How can I do it all at once?
Seems to me that your MyModel instance must be saved into database before saving any relationships. It makes sense because for the relationships, the MyModel's id is needed. So you can change the order of the save method like this:
def save(self, *args, **kwargs):
# notice that super class save go first.
super(MyModel, self).save(*args, **kwargs)
# also this cicle doesn't make sense since self is a newly
# created instance so it won't have anythin in field_m2m.all()
for inst in self.field_m2m.all():
self.tags.add(Tag.objects.get(name=inst.name))
# this should work ok if get returns any tag.
self.tags.add(Tag.objects.get(name=self.field_fk.name))
Hope this helps!
Figured it out thanks to this hint: https://stackoverflow.com/a/6200451/1344854
Model save() method stays default. First I tied a tag to my FkModel and M2mModel instances. The rest of the job is done in ModelAdmin.
# models.py
class FkModel(models.Model):
name = models.CharField(max_length=255, unique=True)
tag = models.ForeignKey(Tag, blank=True, null=True, related_name='fk')
...
class M2mModel(models.Model):
name = models.CharField(max_length=255, unique=True)
tag = models.ForeignKey(Tag, blank=True, null=True, related_name='m2m')
...
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
form.save() # as I learned, without saving at this point, obj.field_m2m.all() will be empty
tags = []
if obj.field_m2m.all():
tags += [m2m.tag for m2m in obj.field_m2m.all()]
if obj.field_fk:
tags += [obj.field_fk.tag]
form.cleaned_data['tags'] = tags
super(MyModelAdmin, self).save_model(request, obj, form, change)
i am developing a django custom field . Inside the custom field def, how can i write some code that saves another field ? For example, inside the def of custom field, I have written pre_save method but after assigning values to other model fields, I have called model_instance.save() method but that resulted in an infinite loop. Can you tell me how to do this ?
class FullNameField(models.CharField):
def contribute_to_class(self, cls, name):
firstname_field = models.CharField(
null=True, blank=True, max_length=50)
lastname_field = models.CharField(
null=True, blank=True, max_length=50)
firstname_field.creation_counter = self.creation_counter
lastname_field.creation_counter = self.creation_counter
cls.add_to_class('firstname', firstname_field )
cls.add_to_class('lastname', lastname_field )
super(FullNameField, self).contribute_to_class(cls, name)
The above code successfully creates the new fields firstname and lastname during syncdb, wha t i want to do is, when i populate the fullnamefield, firstname and lastname should also be populated. This fullname logic is just an example, but the requirement is same.
You can use the pre_save signal to get notified when the class is saved.
Add this to your contribute_to_class method:
from django.db.models.signals import pre_save
def populate_fullname(sender, instance, raw, **kwargs):
if raw:
return # be nice to syncdb
fullname = u'%s %s' % (instance.firstname, instance.lastname))
setattr(instance, name, fullname)
pre_save.connect(populate_fullname, cls)