I'm trying to create an update profile page for my custom User model. In my model, my email field is set to be unique.
class User(UserBase):
...
email = models.EmailField(
max_length=100,
unique=True,
blank=True,
verbose_name='email address',
)
Then in my view I have:
class UpdateProfileView(LoginRequiredMixin, UpdateView):
template_name = 'accounts/update-profile.html'
form_class = UpdateProfileForm
model = User
The only thing that UpdateProfileForm does is check that the old password is different from the new one in the clean method.
My issue is that when I save the form I'm getting the error message User with this Email address already exists.. Since it's an update view and saving a unique field that hasn't changed shouldn't it not throw this error? If this is the correct behavior, then how do I save the form and ignore the email address if it hasn't changed.
Thanks for the help in advance.
Remove blank=True from your User Model definition. The field definition is null=False by default and additionally you specify the field must be unique—it's a important field—so you don't want your form validation to allow blank values. Here is the Django documentation on those attributes. blank is entirely a form validation thing. That alone might fix the error.
Unless you have custom form logic/validation, you don't need the form_class attribute on your UpdateProfileView. From the docs: "These generic views will automatically create a ModelForm". (There is even an UpdateView example).
See if the view works without form_class and if it does then examine your UpdateProfileForm code.
Here are some suggestions/alternatives:
If you stop using Generic Views (i.e. UpdateProfileView), you can then do the following steps in your logic view: if the request is a POST, take the data from the form and update your Model (How to update fields in a model without creating a new record in django?)
Why don't you use a ModelForm instead? https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
Why don't you work with User from django.contrib.auth.models? https://docs.djangoproject.com/en/dev/topics/auth/default/#user-objects
Finally, have you considered working with this already built Django registration app? http://www.michelepasin.org/blog/2011/01/14/setting-up-django-registration/
Never use blank=True and unique=True, its senseless. If you want to make this field is not required in form, just do.
class Form(forms.Form):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['unique_field].required = False
In addition to previous answer, when u use blank=True and unique=True, the "Is already exits ... blah bla" its correct behavior, coz form accepting empty string as value and its already exists. You need to override clean_field method:
class Form(forms.Form):
...
def clean_unique_id(self):
"""
Take new value or if its '' e.g. None take initial value
"""
return self.data['unique_id'] or self.initial['unique_id']
Related
I've two models:
First one:
class A(models.Model):
a_user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
a_title = models.CharField("A title", max_length=500)
Second one:
class B(models.Model):
b_a = models.ForeignKey(A, verbose_name=('A'), unique=False, on_delete=models.CASCADE)
b_details = models.TextField()
Now, I'm using CreateView to create form for Value filling :
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
Then using this to render these field in templates.
Now, my problem is, while giving the field b_a ( which is the dropdown ), it list downs all the values of model A, but the need is to list only the values of model A which belongs to the particular logged in user, in the dropdown.
I've seen all the answers, but still not able to solve the problem.
The things I've tried:
limit_choices_to in models : Not able to pass the value of A in the limit_choices
form_valid : Don't have the model A in the CreateView, as only B is reffered model in B_Create
passing primary key of A in templates via url : Then there is no instance of A in the template so can't access. Also, don't want to handle it in templates.
I'm new to Django and still learning, so don't know to override admin form.
Please suggest the implemented way, if possible to the problem. I've researched and tried most of the similar questions with no result for my particular problem. I feel like, this is a dumb question to ask, but I'm stuck here, so need help.
Thanks..
(Please feel free to suggest corrections.)
You have access to self.request.user in the form_valid of the view. But in order to limit the choices in the form you have to customize the form before it is served initially. You best override the view's get_form and set the form field's queryset:
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
def get_form(self, *args, **kwargs):
form = super(B_Create, self).get_form(*args, **kwargs)
form.fields['b_a'].queryset = self.request.user.a_set.all()
# form.fields['b_a'].queryset = A.objects.filter(a_user=self.request.user)
return form
Generally, there are three places where you can influence the choices of a ModelChoiceField:
If the choices need no runtime knowledge of your data, user, or form instance, and are the same in every context where a modelform might be used, you can set limit_choices_to on the ForeignKey field itself; as module level code, this is evaluated once at module import time. The according query will be built and executed every time a form is rendered.
If the choices need no runtime knowledge, but might be different in different forms, you can use custom ModelForms and set the queryset in the field definition of the respective form field.
If the queryset needs any runtime information, you can either override the __init__ of a custom form and pass it any information it needs to set the field's queryset or you just modify the queryset on the form after it is created which often is a quicker fix and django's default views provide nice hooks to do that (see the code above).
The #schwobaseggl answer is excellent.
Here is a Python 3 version. I needed to limit the projects dropdown input based on the logged-in user.
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
template_name = 'brand/product-create.html'
fields = '__all__'
def get_form(self, form_class=None):
form = super().get_form(form_class=None)
form.fields['project'].queryset = form.fields['project'].queryset.filter(owner_id=self.request.user.id)
return form
I'm a total Django newbie and apologize in advance if I'm not using the correct terminology.
I'm using django-registration to register user on my web-app. I've successfully adapted it work with my custom user model GeneralUser.
Conceptually, each GeneralUser has a Business, another model that I've defined. Whether this is the correct decision or not, I've decided that I never want to register a user without a related Business object.
I've read countless threads on customizing django form, and finally after a few days of unsuccessful attempts, came upon an answer that helped reach a solution. However, I am unsure that my code is correct/safe. This is my adaptation, followed by the linked-answer:
my adaptation:
class GeneralUserForm(UserCreationForm):
business_name = forms.CharField(required=True)
class Meta:
model = GeneralUser
fields = ['username', 'email', 'password1',
'password2', 'business_name']
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=True)
business = Business(name=user.business_name, owner=user)
# notice: no if-block
user.save()
business.save()
# notice: returning only a user-instance
return user
This code successfully creates a user and a business object, and creates the relationship. Looking at the original answer code though, I wonder if there isn't something critical I'm missing:
Answer I based my code on:
class UserCreateForm(UserCreationForm):
job_title = forms.CharField(max_length=100, required=True)
age = forms.IntegerField(required=True)
class Meta:
model = User
def save(self, commit=True):
if not commit:
raise NotImplementedError("Can't create User and UserProfile without database save")
user = super(UserCreateForm, self).save(commit=True)
user_profile = UserProfile(user=user, job_title=self.cleaned_data['job_title'],
age=self.cleaned_data['age'])
user_profile.save()
# notice: multiple returns
return user, user_profile
A few questions about the differences:
Why doesn't my code work if I end it like this:
.
if commit:
user.save()
business.save()
return user
I'm not using cleaned_data, is that okay?
What is the purpose of the if not commit block in the original code?
Most importantly, is this a "legitimate way" to handle user registration that requires an automatic object-relation on creation?
cleaned_data is the dictionary of data after all validation in every field in that form. Now you can decide whether to rely on it or not(preferably you should).So as a pseudocode we could say cleaned_data + errors will be all fields.
commit is used to decide whether it should commit to db(write). From the above code, to add a related model object like profile, the original object(User) has to be created first.Thats why it make force commit.
To add a related object on object creation, there are multiple ways like post_save signals, override model save, override form save etc.So you are using one of good approach, I would say.
I'm struggling to get my head round django forms.. I've been reading various documentation but just can't quite grasp the concepts. I have got to grips with models, views and templates. What I am trying to do is to create a form with various fields composing of dropdown lists and checkboxes which are populated by values in a database.
I have a working app called vms. Using the models.py I have a built a simple schema that holds size and type. Size consists of 'small', 'medium' & 'large'. Type is 'windows' & 'linux'. Using the admin site, I can add an extra size, for example 'Extra Large'.
What I would like to do is create a form that has a drop down list of the vm sizes. If an extra size gets added via the admin site, I would like that size to appear in the drop down list.
I would submit my attempts at the code, but actually am struggling with the concepts. Can anyone help guide me in how to accomplish the above?
Thanks
Oli
Forms are just a tool to simplify and speed-up (the development of) the process of fetching POST data from the request. A manual way would be to do request.POST.get('somefield') for all the fields there are in some HTML form. But Django can do better than that...
In its essence, a Form class holds a number of Fields and performs these tasks:
display HTML inputs,
collect and validate data when user submits it,
if fields don't validate, return the values along with error messages to HTML,
if all fields validate, provide form.cleaned_data dictionary as a convenient way to access these values in view.
With these values, I could then manually create a new instance of a MyModel and save it. Of course, I would have to define a Field in the Form for every Field in MyModel model.
This means that, basically, I could do something like this:
(forgive me for not testing this code, so I can't vouch that it's 100% correct)
models.py:
class MyModel(models.Model):
field1 = models.CharField(max_length=40, blank=False, null=False)
field2 = models.CharField(max_length=60, blank=True, null=True)
forms.py:
class FormForMyModel(forms.Form):
form_field1 = forms.CharField(max_length=40, required=True)
form_field2 = forms.CharField(max_length=60, required=False)
views.py:
def create_a_my_model(request):
if request.method == 'POST':
form = FormForMyModel(request.POST)
if form.is_valid():
my_model = MyModel()
my_model.field1 = form.cleaned_data.get('form_field1', 'default1')
my_model.field2 = form.cleaned_data.get('form_field2', 'default2')
my_model.save()
else:
form = FormForMyModel()
context_data = {'form': form}
return HttpResponse('templtate.html', context_data)
(this could be written with a few lines of code less, but it's meant to be as clear as possible)
Notice there are no relation between model Fields and form Fields! We have to manually assign values to MyModel instance when creating it.
The above example outlines generic form workflow. It is often needed in complex situations, but not in such a simple one as is this example.
For this example (and a LOT of real-world examples), Django can do better than that...
You can notice two annoying issues in the above example:
I have to define Fields on MyModel and Fields on FormForMyModel separately. However, there is a lot of similarity between those two groups (types) of Fields, so that's kind of duplicate work. The similarity grows when adding labels, validators, etc.
creating of MyModel instance is a bit silly, having to assign all those values manually.
This is where a ModelForm comes in.
These act basically just like a regular form (actually, they are extended from regular forms), but they can save me some of the work (the two issues I just outlined, of course :) ).
So back to the two issues:
Instead of defining a form Field for each model Field, I simply define model = MyModel in the the Meta class. This instructs the Form to automatically generate form Fields from model Fields.
Model forms have save method available. This can be used to create instance of model in one line in the view, instead of manually assigning field-by-field.
So, lets make the example above with a ModelForm:
models.py:
class MyModel(models.Model):
field1 = models.CharField(max_length=40, blank=False, null=False)
field2 = models.CharField(max_length=60, blank=True, null=True)
forms.py:
class MyModelForm(forms.ModelForm): # extending ModelForm, not Form as before
class Meta:
model = MyModel
views.py:
def create_a_my_model(request):
if request.method == 'POST':
form = MyModelForm(request.POST)
if form.is_valid():
# save the model to database, directly from the form:
my_model = form.save() # reference to my_model is often not needed at all, a simple form.save() is ok
# alternatively:
# my_model = form.save(commit=False) # create model, but don't save to database
# my.model.something = whatever # if I need to do something before saving it
# my.model.save()
else:
form = MyModelForm()
context_data = {'form': form}
return HttpResponse('templtate.html', context_data)
Hope this clears up the usage of Django forms a bit.
Just one more note - it is perfectly ok to define form Fields on a ModelForm. These will not be used in form.save() but can still be access with form.cleaned_data just as in a regular Form.
Have you tried working with ModelForms before? As I understand, you're looking to create a form based on the model you created right?
Lets say your model is called Temp. You can create a form that correlates with this model (and your question) like this:
forms.py
from django.forms import ModelForm
class TempForm(ModelForm):
class Meta:
model = Temp
The ModelForm will automatically map the selections/choices from your model to a form version.
If you plan on using this in a template later, doing something like this will automatically create a drop-down menu with choices:
<form>
<label for="id_size">Size</label>
{{ form.size }}
</form>
Hope that answers your question!
Simply use CharField in your modelform as below:
SIZES_CHOICES = (
('size1', 'M'),
('size2', 'L'),
)
size = models.CharField(max_length=100, choices=SIZES_CHOICES, default=size1)
in the above code, size1 is the value which will be going to store in your database as name 'size1' and in the drop-down menu, there will be an option is 'M' of right side.you can mentioned any name to these options.
Is there an easy way to allow for required profile fields?
I am using userena in my current django project. I have a custom profile called UserProfile which has a start_year none-blank, non-null field as defined below.
class UserProfile(UserenaBaseProfile, PybbProfile):
user = models.OneToOneField(User, unique=True,
verbose_name=_('user'),
related_name='user_profile')
start_year = models.IntegerField(max_length=4)
I need this to be filled-in on signup. I created a SignupExtraForm as defined below, to override the default form.
class SignupFormExtra(SignupForm):
start_year = forms.IntegerField(label=_(u'Initiation Year'),
min_value=1800,
max_value=datetime.now().year,
required=True)
def save(self):
new_user = super(SignupFormExtra, self).save()
new_user_profile = new_user.get_profile()
new_user_profile.start_year = self.cleaned_data['start_year']
new_user_profile.save()
# Userena expects to get the new user from this form, so return the new
# user.
return new_user
When I attempt to add a new user thru the now modified form I get the below error:
profile_userprofile.start_year may not be NULL
With the stack trace pointing at new_user = super(SignupFormExtra, self).save()), in the code above.
I think this has to do with the user profile being created and saved before I am able to give it the required data from the form. Is there an easy way of supplying this data to the user_creation process, or delaying the creating of the user profile?
Thanks
Shon
UserProfile is created after the User is saved by a post_save signal. Even if you override it with your own signal, you won't have access to the form data from there.
The easiest solution is to just allow start_year to be NULL. It's not necessary to enforce this at the database level, and you can make the field required in all forms either way:
start_year = models.IntegerField(max_length=4, blank=False, null=True)
Your custom form already enforces that the field is required, so you're done.
UPDATE (from comment)
Custom form, yes, but you can still use a ModelForm:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['start_year'].required = True
UPDATE (again)
Actually, I didn't think before my last update. I put blank=False on the start_year field in my example. That will force that the field is required in all ModelForms by default. You don't need a custom ModelForm at all. However, I left the previous update for posterity.
I need set field value, not passed to Django Form constructor.
I have model and form like this:
class Message(models.Model):
created = models.DateTimeField()
text = models.CharField(max_length=200, blank=True, null=True)
active = models.BooleanField(default=False)
class MessageForm(forms.ModelForm):
class Meta:
model = Message
exclude = ('created', 'active')
def clean(self):
# check if user is blocked
if user.is_admin():
self.cleaned_data['active'] = True
return self.cleaned_data
Expected: if current user is admin - I need automatically set message as active. User should not pass this parameter by form.
Actual: I see that saved message always have flag "False" (I can delete condition and in this case I also see that message is not active).
Please help me understand, how can I do set this "active" flag in clean() method.
The previous answer would work, but I like encapsulating all the form's internal operations like what to show and what not, within the form. I know you mentioned you don't want to send a field value to the constructor, but if you don't mind sending the user, your solution would work.
i.e., your constructor:
def __init__(self, user):
self.user = user
super(BaseForm, self).__init__()
then in your clean, you just change the user to self.user.
There is another added benefit to this. Say tomorrow you want to assign more fields based on your user, you don't need to add anything to the views, you can simply add it to the form.
EDIT:
When you add a field to exclude, it is not available in the cleaned data. Instead, set its widget as hidden.
active = forms.BooleanField(widget=forms.HiddenInput)
EDIT 2: If you really don't want the field in the form
In this case, instead of overriding the clean, why don't you override the save?
def save (self):
super(BaseForm, self).save()
if user.is_admin():
self.instance.active=True
super(BaseForm, self).save()
Don't do this in the form's clean() method, do this in the view.
def your_view(request):
if request.method == 'POST':
form = MessageForm(data=request.POST)
if form.is_valid():
new_message = form.save(commit=False)
if user.is_admin():
new_message.active = True
However, if you also want to handle the case where your user is not admin using the same form, you can look at incorporating similar logic in the form's init() instead of the view, probably by passing info about the user from the view to the form's init()
Use this:
def message_form_factory(user):
class MessageForm(forms.ModelForm):
def clean(self):
# check if user is blocked
if user.is_admin():
self.cleaned_data['active'] = True
return self.cleaned_data
return MessageForm
And in your view use:
form = message_form_factory(request.user)()
form = message_form_factory(request.user)(request.POST)