Get the django user in save while using django.form - python

I have a problem getting the user in django when I use django forms. My code looks something like this.
The view:
#login_required
def something(request):
item = ItemForm(request.POST)
item.save(user=request.user)
The form:
class ItemForm(ModelForm):
class Meta:
model = Item
fields = '__all__'
def save(self, *args, **kwargs):
user = kwargs['user']
super(ItemForm, self).save(user=user)
The model
class Item(models.Model):
field = models.CharField(max_length=100,)
field2 = models.CharField(max_length=100,)
def check_permissions(self, user):
return user.groups.filter(name='group').exists()
def save(self, *args, **kwargs):
if self.check_permissions(kwargs['user']):
super(Item, self).save()
My problem is that when I call the default save in ItemForm I get an error because the user param is unexpected. I need the user in the model to make the permission check but I dont know how to get it.

I finally solved the problem. The way I found was to save the form without the user but with the commit flag set to False and then calling the function save from the model with the user param.
The form save method now looks like this
def save(self, *args, **kwargs):
item = super(ItemForm, self).save(commit=False)
item.save(user=kwargs['user'])

Related

Automatically updating a model field when it is created

I would like to automatically update a model field when it is created. So this is my situation. I have a custom User model that my customer can use to login. After they login, they will go to the account/profile page, which has a link to a form. Currently, when the user submits the form, it creates an instance of the LevelTest model(which is something I just need for the website to work). Here is the view class for the form:
class LevelTestView(generic.CreateView):
template_name = "leads/leveltest.html"
form_class = LevelTestModelForm
def get_success_url(self):
return reverse("profile-page")
and here is the LevelTestModelForm:
class LevelTestModelForm(forms.ModelForm):
class Meta:
model = LevelTest
fields = (
'first_name',
'last_name',
'age',
'username',
)
What I want to fill in automatically is the username field. In fact, I wish it doesn't even show up on the form itself when the user types in. The username is a field in the User Model, so I just want the new LevelTest's username field filled in with the current user's username. Hence, I used a post_save signal like below(which doesn't work):
def post_leveltest_created_signal(sender, instance, created, **kwargs):
if created:
instance.objects.update(
username=instance.user.username,
description='Add Description',
phone_number=instance.user.cellphone,
email=instance.user.username,
)
post_save.connect(post_leveltest_created_signal, sender=LevelTest)
I hope you guys could help me tweek the post_save signal, so that when the user creates a LevelTest instance, the LevelTest's username field(as well as the phone_number and email) is filled in with the user model's information. Thanks a lot!
If I understand you correct, you don't need to use signals, you can save username easier:
Extend get_form_kwargs method in your CreateView, like that:
class LevelTestView:(generic.CreateView)
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Extend __init__ and save method in your Form, like that:
class LevelTestModelForm(forms.ModelForm):
...
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
...
def save(self, commit=True):
leveltest = super().save(commit=False)
# I think it would be better if you saved only 'user' instance
# like this - leveltest.user = self.user (of course if you have fk to user model)
leveltest.username = self.user.username
leveltest.phone_number=self.user.cellphone
leveltest.email=self.user.username
leveltest.save()
return leveltest
I think #KIN1991's answer is pretty awesome, but you can minimize/optimize the code even more by just overriding the form_valid method. Like this:
class LevelTestView:(generic.CreateView)
...
def form_valid(self, form, *args, **kwargs):
user = self.request.user
form.instance.username = user.username
form.instance.phone_number=user.cellphone,
form.instance.email=user.username
return super().form_valid(form, *args, **kwargs)

Django: how to automatically also delete file from storage when Clear Checkbox ticked?

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!

Modify variable just in the Django admin but not in database

I want to modify a single value when I open an instance of a model in the Django admin.
Basically I want to display a value that is the opposite of the one stored in the database. So here is what I did in admin.py :
class MyModelAdminForm(forms.ModelForm):
import_file = forms.FileField(required=False)
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyModelAdminForm, self).__init__(*args, **kwargs)
print(self.instance.value)
self.instance.value = self.instance.get_reverted_value()
print(self.instance.value)
I works I can see it with the prints, but the admin still displays the original value that is stored in database.
Any idea ?
Thanks in advance
What about:
def __init__(self, *args, **kwargs):
super(MyModelAdminForm, self).__init__(*args, **kwargs)
self.fields['value'].value = self.instance.get_reverted_value()

Add a field to the ModelAdmin Form

I want to give users the possibility to create multiple events at once. Therefore I would like to add a field to the admin-add-page where a number of repetitions can be specified. Then I want to override the save function and create multiple events (based on the input). I started writing some code but the admin add page does not update at all. I will show you the code below:
In admins.py:
class EventAdmin(admin.ModelAdmin):
form = EventForm
admin.site.register(Event, EventAdmin)
In forms.py
from django import forms
from django.db import models
from calendar_app.models import Event
class EventForm(forms.ModelForm):
name = models.CharField(max_length=100) # just for testing purpose
class Meta:
model = Event
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
if not kwargs.has_key('instance'):
self.fields['name'] = forms.CharField(label='Name')
self.base_fields['name'] = forms.CharField(label='Name')
def save(self, commit=True):
model = super(EventForm, self).save(commit=False)
# Save all the fields...
if commit:
model.save()
return model
But the "name" field is not showing up when I add an event. Any ideas? Thanks!
I used models.CharField instead of forms.CharField. See comments.

Django: form save exculde certain field

I do not understand the official document about exclude .
Set the exclude attribute of the ModelForm‘s inner Meta class to a list of fields to be excluded from the form.
For example:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
Since the Author model has the 3 fields name, title and birth_date, this will result in the fields name and birth_date being present on the form.
My understanding is as follows: django form save method will save all form data.If one set exclude =('something',) , 'something' field will not show on frontend and wouldn't be save while calling form save method.
But when I do as the document saying, 'something' field still show.What's the matter?
I also want to add some fields to a form for validating which can show on frontend without saving.It is stange that I find nothing about this need.
**update**
my code :
class ProfileForm(Html5Mixin, forms.ModelForm):
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(render_value=False))
password2 = forms.CharField(label=_("Password (again)"),
widget=forms.PasswordInput(render_value=False))
captcha_text = forms.CharField(label=_("captcha"),
widget=forms.TextInput())
captcha_detext = forms.CharField(
widget=forms.HiddenInput())
class Meta:
model = User
fields = ("email", "username")
exclude = ['captcha_text']
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
..........
def clean_username(self):
.....
def clean_password2(self):
....
def save(self, *args, **kwargs):
"""
Create the new user. If no username is supplied (may be hidden
via ``ACCOUNTS_PROFILE_FORM_EXCLUDE_FIELDS`` or
``ACCOUNTS_NO_USERNAME``), we generate a unique username, so
that if profile pages are enabled, we still have something to
use as the profile's slug.
"""
..............
def get_profile_fields_form(self):
return ProfileFieldsForm
if exclude only affect the model defined under class Meta , so exclude = ['captcha_text'] would not work?
exclude = ['title'] will exclude the field from the form, not from the model.
form.save() will try to save the model instance with the available for fields, but model might throw any error pertaining to the missing field.
To add extra fields in model form, do this:
class PartialAuthorForm (ModelForm):
extra_field = forms.IntegerField()
class Meta:
model = Author
def save(self, *args, **kwargs):
# do something with self.cleaned_data['extra_field']
super(PartialAuthorForm, self).save(*args, **kwargs)
But make sure there is no field called "PartialAuthorForm" in the model Author.
First, the reason why your title field is still displayed must be somewhere in your view. Be sure that you create your (unbound) form instance like this:
form = PartialAuthorForm()
and try this simple rendering method in the template
{{ form.as_p }}
Second, it should be no problem to add extra fields to a model form, see e.g. this post.

Categories