All pages in my Django website have a footer link "Feedback/Questions". If the new person comes to the site and clicks that link, they should be directed to a form with a pulldown to indicate if they have feedback versus a question and fields for their email address and their feedback or question. The page will have a simple header all non-authenticated users will see. On the other hand, if a site member signs in and is authenticated, they should see the same form but without the email field (since I already know their email address) and a different authenticated header containing the site's internal navbar, buttons, etc.
My initial thought was to create an abstract class FeedbackQuestion:
class FeedbackQuestion(models.Model):
submission_type = ... (type, i.e. feedback or question)
submission_text = ... (actual feedback or question)
...
class Meta:
abstract = True
Then I'd create two separate concrete child classes:
class AnonFeedbackQuestion(FeedbackQuestion):
email = models.EmailField(...)
class Meta:
db_table = anon_feedback_question
class AuthFeedbackQuestion(FeedbackQuestion):
user = models.ForeignKey(User, related_name="user")
class Meta:
db_table = auth_feedback_question
These two classes would have their own model forms:
class AnonFeedbackQuestionForm(ModelForm):
class Meta:
model = AnonFeedbackQuestion
fields = ['submission_type', 'submission_text', 'email']
class AuthFeedbackQuestionForm(ModelForm):
class Meta:
model = AuthFeedbackQuestion
fields = ['submission_type', 'submission_text']
The problem I forsee is that I will have to do the following in my view that displays the feedback form:
def get_feedback_questions(request, template):
if request.method == 'POST':
...
if request.user.is_authenticated():
form = AuthFeedbackQuestionForm(request.POST)
else:
form = AnonFeedbackQuestionForm(request.POST)
if form.is_valid():
(process form)
...
else:
if request.user.is_authenticated():
form = AuthFeedbackQuestionForm(request.POST)
else:
form = AnonFeedbackQuestionForm(request.POST)
...
context = {'form': form}
return render(request, template, context)
Having to repeat these if/then/else blocks to identify which form to use seems rather inelegant. Is there a better, cleaner "Django" way to do this?
Thanks!
I wouldn't subclass your models - if it's an anonymous question you could just include a user attribute as well as an email attribute on one model with blank=True and null=True:
class FeedbackQuestion(models.Model):
submission_type = ... (type, i.e. feedback or question)
submission_text = ... (actual feedback or question)
email = models.EmailField(..., blank=True, null=True)
user = models.ForeignKey(User, related_name="user", blank=True, null=True)
...
class Meta:
abstract = True
This way you can add either the email for an anonymous user's feedback/question or the user if they're authenticated.
Then I'd combine your forms into one including the email field, but remove the email field depending on if the user is authenticated (see this answer):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(UserForm, self).__init__(*args, **kwargs)
if self.user:
# For logged-in users, email field not necessary
self.fields.pop('email')
else:
# Otherwise, the field needs to be required
self.fields['email'].required = True
Then you just need to make sure you create the user appropriately as you clean the form's data (e.g., make sure the email address isn't already taken, etc.)
Related
SOLUTION AT THE BOTTOM
Problem: Django form populating with list of objects rather than values
Summary: I have 2 models Entities and Breaks. Breaks has a FK relationship to the entity_id (not the PK) on the Entities model.
I want to generate an empty form for all the fields of Breaks. Generating a basic form populates all the empty fields, but for the FK it generates a dropdown list of all objects of the Entities table. This is not helpful so I have excluded this in the ModelForm below and tried to replace with a list of all the entity_ids of the Entities table. This form renders as expected.
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
exclude = ('entity',)
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all().values_list('entity_id', flat=True))
The below FormView is the cbv called by the URL. As the below stands if I populate the form, and for the FK column entity_id choose one of the values, the form will not submit. By that field on the form template the following message appears Select a valid choice. That choice is not one of the available choices.
class ContactFormView(FormView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
My initial thoughts were either that the datatype of this field (string/integer) was wrong or that Django needed the PK of the row in the Entities table (for whatever reason).
So I added a post function to the FormView and could see that the request.body was populating correctly. However I can't work out how to populate this into the ModelForm and save to the database, or overcome the issue mentioned above.
Addendum:
Models added below:
class Entity(models.Model):
pk_securities = models.AutoField(primary_key=True)
entity_id = models.CharField(unique=True)
entity_description = models.CharField(blank=True, null=True)
class Meta:
managed = False
db_table = 'entities'
class Breaks(models.Model):
pk_break = models.AutoField(primary_key=True)
date = models.DateField(blank=True, null=True)
entity = models.ForeignKey(Entity, on_delete= models.CASCADE, to_field='entity_id')
commentary = models.CharField(blank=True, null=True)
active = models.BooleanField()
def get_absolute_url(self):
return reverse(
"item-update", args=[str(self.pk_break)]
)
def __str__(self):
return f"{self.pk_break}"
class Meta:
managed = False
db_table = 'breaks'
SOLUTION
Firstly I got this working by adding the following to the Entity Model class. However I didn't like this as it would have consequences elsewhere.
def __str__(self):
return f"{self.entity_id}"
I found this SO thread on the topic. The accepted answer is fantastic and the comments to it are helpful.
The solution is to subclass ModelChoiceField and override the label_from_instance
class EntityChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.entity_id
I think your problem is two fold, first is not rendering the dropdown correctly and second is form is not saving. For first problem, you do not need to do any changes in ModelChoiceField queryset, instead, add to_field_name:
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all(), to_field_name='entity_id')
Secondly, if you want to save the form, instead of FormView, use CreateView:
class ContactFormView(CreateView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
model = Breaks
In Django, the request object passed as parameter to your view has an attribute called "method" where the type of the request is set, and all data passed via POST can be accessed via the request. POST dictionary. The view will display the result of the login form posted through the loggedin. html.
I have a form that allows the user to pick several vans (many-to-many relationship). Each van has a boolean attribute named "available". I want to only show the vans whose "available" attribute is set to "True". How do I do this in the forms.py file?
I know that this could possibly be done in the template, but I did not want to create a new form-template with each individual field written out. I wanted to know if this functionality could be done in the forms.py file or in the class based view. I believe that doing it that way would be a bit cleaner. I've look into the validators but I don't think this is the way to go. Maybe I need to run a query set in the form file that checks the attribute before passing it to the form template?
views.py
def post(self, request):
"""Take in user data, clean it, and then post it to the database."""
form = self.form_class(request.POST) # pass in the user's data to that was submitted in form
if form.is_valid():
trip = form.save(commit=False) # create an object so we can clean the data before saving it
# now get the clean and normalize the data
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
trip_start = form.cleaned_data['trip_start']
trip_end = form.cleaned_data['trip_end']
van_used = form.cleaned_data['van_used']
trip.save()
forms.py
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
models.py
class trips(models.Model):
class Meta:
verbose_name_plural = "trips"
van_used = models.ManyToManyField(vans)
class vans(models.Model):
class Meta:
verbose_name_plural = "vans"
vanName = models.CharField(max_length=30, unique=True, blank=False)
available = models.BooleanField(default=True, blank=False)
# set up how the vans will be referenced in the admin page
def __str__(self):
return self.vanName
The final form that is rendered would only show the vans whose "available" attribute is set to True. Thanks in advance.
You have to override queryset for van_used field in form like this.
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['van_used'].queryset = vans.objects.filter(available=True)
I have created a function that can save multiple goals per one user and display them in an html file. The issue is once I logout, I cannot log back in with the same user as I get the error User object has no attribute Goals, even though it is saved in the database. My question is what is causing this error, the references to goals in my view maybe, and what is a potential solution? Thank you!
models.py
class Goals(models.Model):
user = models.ForeignKey(User, null=True, default=None, on_delete=models.CASCADE)
goal = models.CharField(max_length=2000)
instrument = models.CharField(max_length=255, choices=instrument_list, blank=True)
goal_date = models.DateField(auto_now=False, auto_now_add=False)
def __str__(self):
return self.Goals
#receiver(post_save, sender=User)
def create_user_goals(sender, instance, created, **kwargs):
if created:
Goals.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_goals(sender, instance, **kwargs):
instance.Goals.save()
class GoalsForm(ModelForm):
class Meta:
model = Goals
exclude = ('user',)
views.py
def goal_creation(request):
form = GoalsForm()
cur_goals = Goals.objects.filter(user=request.user)
if request.method == 'POST':
form = GoalsForm(request.POST)
if form.is_valid():
goals = form.save(commit=False)
goals.user = request.user
goals.save()
cur_goals = Goals.objects.filter(user=request.user)
return redirect('/student/goal-progress')
else:
form = GoalsForm()
context = {'form' : form, 'goals': cur_goals}
return render(request, 'student/goal_creation.html', context)
You have two issues:
You can't access child instances using instance.Goals; you should use instance.goals_set.
You can't save a queryset. You should save Goals instances one by one, i.e.
for goal in instance.goals_set.all():
goal.save()
That being said, I recommend you to rename your Goals class to Goal as it will create confusion with Django's naming conventions. It also makes sense because each row represents a single goal.
Try adding related_name='goals' to user field definition of Goals class:
user = models.ForeignKey(User, related_name='goals', null=True, default=None, on_delete=models.CASCADE)
Then, you should be able to access this property on the user's object:
user_instance.goals.all().
Migration might be required.
Although this is not directly related to the issue, I think that it's better to name the model class in singular form "Goal", it will be consistent with other model's names (model represents one object=one row) and avoid ambiguity in automatic pluralization.
Let me explain what my problem is in more detail.
First I have a class 'UserInfo' which connected to User class of django.contrib.auth.models like below
models.py
class UserInfo(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(max_length=15,blank=True,unique=True)
position = models.CharField(max_length=15,blank=True,unique=True)
class Meta:
default_permissions = ()
def __str__(self):
return self.position
then I wanted to use ModelForm because I can write less codes. The reason why I made CreateNewUser class is that I wanted to let user can see only [username, email, groups, user_permissions] and control those. (to prevent them to select superuser or staff or inactive options)
forms.py
class CreateNewUserInfo(forms.ModelForm):
class Meta:
model = UserInfo
fields = '__all__'
class CreateNewUser(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email', 'groups', 'user_permissions']
problem happened in here. I wanted to use FormView to use generic view with class typed view so that I can write less codes(concise code). there is attribute named 'form_class' and I couldn't put two different class with it. I wanted to put different two class to one form with generic view.
views.py
class TestView(FormView):
form_class = CustomForm
template_name = 'manager/alltoall.html'
def form_valid(self, form):
At the end, I made new class in forms.py and wrote every field which I need like below.
forms.py
class CustomForm(forms.Form):
username = forms.CharField(initial='testname',max_length=150)
email = forms.EmailField()
phone_number = forms.CharField(max_length=15)
position = forms.CharField(max_length=15)
views.py
class TestView(FormView):
form_class = CustomForm
template_name = 'manager/alltoall.html'
def form_valid(self, form):
username = form.cleaned_data['username']
email = form.cleaned_data['email']
phone_number = form.cleaned_data['phone_number']
position = form.cleaned_data['position']
with transaction.atomic():
user = User.objects.create(username=username,email=email)
userinfo = UserInfo.objects.create(user=user,phone=phone_number,position=position)
userinfo.save()
user.save()
return super(TestView, self).form_valid(form)
Is there anyway to use ModelForm and FormView to show two different class in a form at the same time? Additionally, I used transaction like above because I wanted to save data in database with two different class. Is it right approach or Is there any other way to do that more conveniently(or efficiently) with built in functions in django?
Thank you for taking your time to read all. I wonder if it is too long, but I wanted to deliver what I wanted to know exactly. Thank you!
I have a web application that allows users to create an account, and in doing so creates a user object form a the standard Django User model, associated with a custom UserProfile model, as well as an Address model. I have built an HTML form that allows the user to update their address, and profile, by means of using a ContactInfoForm that subclasses both the AddressForm and UserProfileForm; both of which are ModelForms, as follows:
class AddressForm(forms.ModelForm):
class Meta:
model = common_models.Address
exclude = ('updated_dt','address_type','created_dt')
def __init__(self,*args,**kwargs):
super(AddressForm,self).__init__(*args,**kwargs)
firstname = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Enter First Name'})
lastname = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Enter Last Name'})
address1 = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Enter Address'})
etc...
class UserProfileForm(forms.ModelForm):
class Meta:
model = common_models.UserProfile
exclude = ('created_dt','updated_dt','entity_active','profile_hash','user','address')
account_type = forms.ChoiceField(choices=account_choices,widget=forms.Select(attrs={'id':'account_type_list'}),error_messages={'required':'Please Select Account Type'})
name = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Company Name'})
supplier_type = forms.ModelChoiceField(queryset=common_models.SupplierTypeCode.objects.all(),required=False,widget=forms.Select(attrs={'id':'account_type_select'}))
buyer_type = forms.ModelChoiceField(queryset=common_models.ClientTypeCode.objects.all(),widget=forms.Select(attrs={'id':'account_type_select'}),required=False)
class ContactInfoForm(AddressForm,UserProfileForm):
class Meta:
model = common_models.User
exclude = ('email','username',
'password','last_login','date_joined')
def __init__(self,user=None,request_post_data=None,*args,**kwargs):
if not request_post_data:
params = dict([tuple([k,v]) for k,v in user.get_profile().address.__dict__.items()] +
[tuple([k,v]) for k,v in user.get_profile().__dict__.items()])
super(ContactInfoForm,self).__init__(initial=params,*args,**kwargs)
else:
super(ContactInfoForm,self).__init__(request_post_data,instance=user)
Now, I have the following questions:
1) How do I save the ContactInfoForm, such that both the user_profile and the address tables are updated, along with the auth_user table? I have tried overriding the save function in the ContactInfoForm, then calling the save function of Address and UserProfile as follows:
def save(self):
address = AddressForm.save(self)
profile = UserProfileForm.save(self)
however, that doesn't work as the instance of self is a user object, and thus both the above functions return a user object
2) Is my implementation of the init method of the ContactInfoForm the best way to pre-populate the HTML form when the user first visits the update contact info page? In other words, is the construction of the params dictionary and using it as the initial argument correct. Keep in my mind, I have access to the user object from request.user since this view is behind a login_required decorator...
3) Is there perhaps a better way to achieve what I am trying to achieve that isn't as complicated and more Django/Pythonic?
Usually in Django such thing is being made by creating 3 separate forms and process them all in one view.
address_form = AddressForm(request.POST)
profile_form = UserProfileForm(request.POST)
contacts_form = ContactInfoForm(request.POST)
if address_form.is_valid() and profile_form.is_valid() and contacts_form.is_valid():
address_form.save()
profile_form.save()
contacts_form.save()
Maybe it's bit more code this way but it's much more clear and easy to read.