Automatically updating a model field when it is created - python

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)

Related

Does Django's get_queryset() in admin prevent malicious object saving?

I am developing a multi-tenant app in Django. In the Django admin, some querysets are filtered based on the user, using get_queryset().
Up till now, when a user updated an object from the Django change form, I would validate the data by creating a ModelAdmin form using a factory function to capture the HttpRequest object, then ensure that the Guest object's user was the current user:
EXAMPLE
models.py
class Guest(models.Model):
guest_name = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE)
admin.py
#admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
def get_queryset(self, request)
qs = super().get_queryset(request)
return qs.filter(user=request.user)
def get_form(self, request, obj=None, **kwargs):
self.form = _guest_admin_form_factory(request)
return super().get_form(request, obj, **kwargs)
forms.py
def _guest_admin_form_factory(request):
class GuestAdminForm(forms.ModelForm):
class Meta:
model = Guest
exclude = []
def clean_user(self):
user = self.cleaned_data.get('user', None)
if not user:
return user
if user != request.user:
raise forms.ValidationError('Invalid request.')
return user
return GuestAdminForm
It occurred to me that Django might use the get_queryset() method to validate this for me, since some simple logging showed that the method is called twice when an object gets updated from the change form.
Is this the case, or do I need to stick to validating through a ModelAdmin form?
The documented way to do this is to define has_change_permission():
#admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).filter(user=request.user)
def has_change_permission(self, request, obj=None):
return (obj is None or obj.user == request.user)
No need to muck about with the form.

Django UpdateView not saving whether new user or existing user using ModelForm

I am writing an UpdateView UpdateAccountView for the User model, updating through a ModelForm MyUserCreationForm which is already the ModelForm used for creating new users. The problem is that whenever I click Submit to save the changes in the template, it rerenders the template.
For instance, if I didn't change any fields, it gives me error of "Username is already taken" which I will show you in the MyUserCreationForm to check for unique usernames, or just rerenders the template for new entries on the fields, without actually saving any changes to the model.
Here is my MyUserCreationForm
class MyUserCreationForm(UserCreationForm):
class Meta:
model = User #extended from auth.models.User
fields = ("first_name", "last_name", "username", "email", "gender", "profile_photo")
# adding bootstrap styling to the ModelForm fields
def __init__(self, *args, **kwargs):
super(MyUserCreationForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control input-lg',
'placeholder': field.replace("_", " ").title(),
'tabindex': list(self.fields).index(field) + 1})
self.fields[field].widget.attrs.pop('autofocus', None)
if field == 'username' or field == 'email':
self.fields[field].widget.attrs.update({
'placeholder': field.replace("_", " ").title() + ' *',
})
def clean_username(self):
username = self.cleaned_data['username']
if not re.search(r'^[\w.-]+$', username):
raise forms.ValidationError('Username can only contain alphanumeric characters, dots, hyphens ,and underscores')
try:
User.objects.get(username=username)
except ObjectDoesNotExist:
return username
raise forms.ValidationError('Username is already taken.')
and here is the view class UpdateAccountView
class UpdateAccountView(UpdateView):
form_class = MyUserCreationForm
model = User
template_name = 'auth/account-edit.html'
success_url = '/'
def get_object(self, queryset=None):
return self.request.user
However, if I directly update the Model by using model and fields in the UpdateView, it works fine. But I need to do it through the ModelForm to have control over the styles when rendering.
So I know that the problem lies within ModelForm but I cannot find it, even after searching a lot.
Thank you in advance.
You could try moving the code that styles the fields in to a separate mixin:
class UserStyleMixin(object):
def __init__(self, *args, **kwargs):
super(UserStyleMixin, self).__init__(*args, **kwargs)
# Style the fields here
Then you can make your MyUserCreationForm use the mixin, and create a new form for the update view.
class MyUserCreationForm(UserStyleMixin, UserCreationForm):
...
class UserUpdateForm(UserStyleMixin, forms.ModelForm):
...
Note that if the update view allows the user to change the username, then you should still check that the username is allowed and unique. If you have unique=True for the username in your model, Django should take care of this for you. It would be a good idea to move the username regex to the model as well.

Get the django user in save while using django.form

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'])

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.

django form exclude a user instance from a queryset

I have the following model:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name="user")
people_interested = models.ManyToManyField(User, related_name="interested")
Now I want a form where I want to offer users a form where they can choose people_interested, so I add the following forms.py
class ChooseForm(forms.Form):
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
and then in views:
form = ChooseForm(data = request.POST or None)
if request.POST and form.is_valid():
uprofile, created = UserProfile.objects.get_or_create(user=request.user)
uprofile.people_interested = form.cleaned_data['peers']
uprofile.save()
return HttpResponseRedirect("/")
else:
return render(request, "form_polls.html", {'form':form})
But the trouble with this is, the current user instance also gets displayed. So I tried the following in views.py:
form = ChooseForm(request.user.id, data = request.POST or None)
and then in forms.py
class ChooseForm(forms.Form):
def __init__(self, uid, *args, **kwargs):
super(ChooseForm, self).__init__(*args, **kwargs)
self.fields['peers'].queryset = User.objects.exclude(id=uid)
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
But the above is not a clean implementation, is there a better method of doing it??
What makes you say this is not a clean implementation? Overwriting queryset on __init__ is perfectly acceptable.
The only things I'd do to improve your code is using a post_save signal on User to create it's UserProfile, then just do user.get_profile() on your view. See this question
You could also use a ModelForm for UserProfile instead of a regular form, and limit the fields to people_interested.

Categories