I'm currently learning Django forms and I came across this post.
One of the forms currently looks like this:
What I'd like to do is to change Category into a formset and be able to render multiple dropdowns while creating a product.
My models.py:
class Category(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=30)
price = models.DecimalField(decimal_places=2, max_digits=10)
category = models.ForeignKey(Category, on_delete = models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
My forms.py:
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ('name', )
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ('name', 'price', 'category', )
def __init__(self, user, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['category'].queryset = Category.objects.filter(user=user)
Current method in views.py:
#login_required
def new_product(request):
if request.method == 'POST':
form = ProductForm(request.user, request.POST)
if form.is_valid():
product = form.save(commit=False)
product.user = request.user
product.save()
return redirect('products_list')
else:
form = ProductForm(request.user)
return render(request, 'products/product_form.html', {'form': form})
products_form.html:
{% extends 'base.html' %}
{% block content %}
<h1>New product</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="save">
cancel
</form>
{% endblock %}
What I tried is to make use of the modelformset_factory and change the method in views.py by creating a CategoryFormSet as:
CategoryFormSet = modelformset_factory(Category, fields=('name', ), extra=2)
formset = CategoryFormSet(data=data, queryset=Category.objects.filter(user=request.user))
then replacing the original form from views.py with the created formset. In the html I simply replace the {{form}} with {{formset}}. After playing around with it for a while, I either get the New product with just a submit button (no form rendered) or a User object has no attribute GET error. What am I doing wrong?
The tutorial focuses on allowing the user to add/update more instances of one model. You want to edit one thing, with multiple related things inline.
However, your data model only allows one category per product, so this does not make any sense. Whether you want more than one category per product, is something only you can answer :) - I'm going to assume you want that.
First you need to change your model to allow for multiple categories per product:
class Product(models.Model):
name = models.CharField(max_length=30)
price = models.DecimalField(decimal_places=2, max_digits=10)
categories = models.ManyToManyField(Category, related_name='products')
user = models.ForeignKey(User, on_delete=models.CASCADE)
And then you need to learn about Inline Formsets.
Come back with a specific if you get stuck on that.
Instead of creating new model Category. You can do this.
CATEGORY_CHOICES= (
("1", "1"),
("2", "2"),
("3", "3"),
("4", "4"),
("5", "5"),
("6", "6"),
("7", "7"),
("8", "8"),
)
category = models.CharField(max_length = 20,choices = CATEGORY_CHOICES,default = '1')
It will automatically render in HTML.
Related
Newbie problem. Per the following script in a template ...
{% if request.user not in Result %}
<p>You have no account in the system.</p>
{% endif %}
... the statement "You have no account in the system." is appearing 100 times on screen---because there are 100 records and therefore the condition is being checked 100 times.
Is there a way to modify the script so that the statement appears just once? Meaning, it checks the entire database once for evidence that request.user appears anywhere in the model in aggregrate (and not whether it appears in each of the 100 records in the database)?
Maybe there's an easier/better way to do this in views.py vs a template, but that's beyond my knowledge. Thank you. Below is the model called Result.
models.py
class Result(models.Model):
custom_user = models.ForeignKey(CustomUser, default=None,
null=True, on_delete=models.SET_NULL)
decision = models.ForeignKey(Decision, default=None,
null=True, on_delete=models.SET_NULL,
verbose_name="Decision")
vote_eligible = models.BooleanField(default=True)
vote = models.CharField(default="", max_length=100,
blank=True, verbose_name="Your
Vote:")
voted_already = models.BooleanField(default=False)
#staticmethod
def get_absolute_url():
return "/home"
def __str__(self):
return f"{self.custom_user}"
views.py
class VoteForm(LoginRequiredMixin, CreateView):
model = Result
form_class = VotingForm
template_name = 'users/vote_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["Result"] = Result.objects.all()
return context
forms.py
class VotingForm(forms.ModelForm):
class Meta:
model = Result
fields = ['decision', 'vote']
views.py
Since the requirement is to display whether the logged in user has account in 'Result' model or not. I have filtered the rows specific to the user. You can loop over the user_specific in your template. If user is present in 'Result' model 'user_specifc' will have elements. If user is not present in 'Result' table, 'user_specific' will be empty. In you template, you can check whether 'user_specific' is empty list or not.
class VoteForm(LoginRequiredMixin, CreateView):
model = Result
form_class = VotingForm
template_name = 'users/vote_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["Result"] = Result.objects.all()
context['user_specific'] = Result.objects.filter(custom_user=self.request.user)
return context
template.html
{% if not user_specific %}
<p>You have no account in the system.</p>
{% endif %}
First of all, I'm new to Django or MVC frameworks in general and I've got quite little experience in Python.
I've read stackoverflow threads with similar title, but yet I'm missing some puzzle piece.
After trying for some time, this is what I ended up with... which renders empty list. I think it's due to the fact, that the referred table has no database entries. I can't seem to figure out how to evaluate values based on FK from another table:
models.py
class Employees(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def __str__(self):
return self.last_name
def get_absolute_url(self):
return reverse('employees')
class Tasks(models.Model):
type = models.CharField(max_length=30)
duration = models.IntegerField()
def __str__(self):
return self.type
def get_absolute_url(self):
return reverse('tasks')
class Records(models.Model):
employee_id = models.ForeignKey(Employees)
task_id = models.ForeignKey(Tasks)
date_from = models.DateTimeField()
date_to = models.DateTimeField()
def __str__(self):
return self.id
def get_absolute_url(self):
return reverse('records')
forms.py
class CreateRecordForm(forms.ModelForm):
employee_id = forms.ModelChoiceField(queryset=Records.objects.all().values('employee_id'))
task_id = forms.ModelChoiceField(queryset=Records.objects.all().values('task_id'))
date_from = forms.DateTimeField() #doesnt matter at the moment
class Meta:
model = Records
fields = ('employee_id', 'task_id', 'date_from')
views.py
class RecordCreateView(CreateView):
form_class = CreateRecordForm
template_name = 'record_new.html'
model = Records
#fields = ['employee_id', 'task_id', 'date_from']
Generic view below renders the drop-down selection correctly, so it is doable.
class RecordCreateView(CreateView):
template_name = 'record_new.html'
model = Records
fields = ['employee_id', 'task_id', 'date_from']
record_new.html
{% extends 'base.html' %}
{% block content %}
<h1>New record</h1>
<form action="" method="post">{% csrf_token %}
{{ form }}
<button class="btn btn-success ml-2" type="submit">save</button>
</form>
{% endblock content %}
Any help is greatly appreciated!
I have Contestant model that has many-to-one relationship with Category. I want the creator of the award to only have access to last 15 categories of the award he has created in instantiating contestant model. That's, the multichoice queryset in the Contestant field(category) will only show the list of the last 15 categories being created in the Category Model. I have made different efforts, but the code is either not working or giving '['ManagementForm data is missing or has been tampered with']' error.
I have made countless re-factorization of codes and I have tried to adopt solutions I found on internet. But they didn't work.
# MY CATEGORY MODEL
class Category(models.Model):
award = models.ForeignKey(Award, on_delete=models.CASCADE)
award_category = models.CharField(max_length=150, blank=True, null=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.award_category
# MY CONTESTANT MODEL
class Contestant(models.Model):
award_name = models.ForeignKey(Award, on_delete=models.CASCADE)
contestant_name = models.CharField(max_length=150, null=True, blank=True )
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='contestants')
vote = models.IntegerField()
def number_of_vote(self):
return Contestant.objects.filter(self.vote).count()
vote_count = property(number_of_vote)
#MY VIEW
def get_award(request, pk):
new_award = get_object_or_404(Award, pk=pk)
Contest_Formset = modelformset_factory(Contestant, fields('contestant_name', 'category',), extra=15)
formset = Contest_Formset(request.POST)
for form in formset:
form.fields['category'].queryset = Category.objects.filter(user=request.user)[1:15]
if request.method == 'POST' and form.is_valid():
myform = form.save(commit=False)
myform.award_name = new_award
myform.save()
return redirect('award_details', pk=new_award.pk)
else:
formset = Contest_Formset()
context = {
'new_award': new_award,
'formset': formset
}
return render(request, 'voting/get_award.html', context)
# TEMPLATE
<form action=" " method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{{ formset }}
<input type="submit" value="save">
</form>
I expect the output of the contestant's category field to show only the last 15 categories created by the login user.
I've extended the django user with a OneToOneField so I can store address and such.
SiteUser is the model which extends User using a OneToOneField. How can I get fields of both User and SiteUser in a single ModelForm?
Here is the relevant code so far:
class ProfileForm(ModelForm):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class AddressForm(ModelForm):
pass
View in question:
def edit_profile(request):
username = request.user
user = User.objects.get(username__exact=username)
profileform_class = ProfileForm
if request.method == 'POST':
profileform = profileform_class(data=request.POST, instance=user)
if profileform.is_valid():
profileform.save()
return redirect('profile')
else:
profileform = profileform_class(instance=user)
return render(request, 'edit_profile.html', {
'user': user,
'profileform': profileform,
})
And the two models:
class Product(models.Model):
order = models.IntegerField(default=0)
name = models.CharField(max_length=255)
description = models.TextField()
image = models.ImageField(upload_to='product-images', default='default.jpg')
price = models.FloatField()
slug = models.SlugField(unique=True)
class SiteUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
address = models.CharField(max_length=255)
post_number = models.CharField(max_length=255, default='')
post_location = models.CharField(max_length=255, default='')
HTML Page I want the forms on:
{% extends 'base.html' %}
{% block title %}
Rediger {{ product.name }} - {{ block.super }}
{% endblock title %}
{% block content %}
<h1>Rediger "{{ user }}"</h1>
<form role="form" action="" method="post">
{% csrf_token %}
{{ profileform.as_p }}
{{ addressform.as_p }}
<button type="submit">Submit</button>
</form>
{% endblock content %}
One option is to use inline formsets. Using this, you won't be needing a second ModelForm.
Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key.
You can find good examples here.
Alternatively, if you want to avoid inline formsets and use both ProfileForm and AddressForm under a single <form> tag as you have done in your template, you can do it like this.
Forms:
class ProfileForm(ModelForm):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class AddressForm(ModelForm):
class Meta:
model = SiteUser
exclude = ['user']
Views:
def edit_profile(request):
username = request.user
user = User.objects.get(username__exact=username)
profileform_class = ProfileForm
addressform_class = AddressForm
if request.method == 'POST':
profileform = profileform_class(data=request.POST, instance=user)
addressform = addressform_class(data=request.POST, instance=user.siteuser)
if all((profileform.is_valid(), addressform.is_valid())):
user = profileform.save()
address = addressform.save(commit=False)
address.user = user
address.save()
return redirect('profile')
else:
profileform = profileform_class(instance=user)
addressform = addressform_class(instance=user.siteuser)
return render(request, 'edit_profile.html', {
'user': user,
'profileform': profileform,
'addressform': addressform,
})
I don't know much about forms, but I think you should use the "initial" parameter when instantiating the AddressForm, as exemplified here: https://docs.djangoproject.com/es/1.9/topics/forms/modelforms/#providing-initial-values
So you create your AddressForm class with SiteUser as model, and when you instantiate it in the view, you make it like this:
AddressForm(initial={'user': request.user})
If "username" is not the primary key of the User model, you can get the primary key like this:
User.objects.get(username=request.user).pk
and then give it in the "initial" parameter.
model:
class Company(models.Model):
name = models.CharField(max_length=150)
description = models.CharField(max_length=150)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Bike(models.Model):
to_company = models.OneToOneField(Company, on_delete=models.CASCADE, related_name="bike_company")
name_bike = models.CharField(max_length=150)
model = models.CharField(max_length=150)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
form.py
class CompanyForm(ModelForm):
class Meta:
model = Company
fields = ("name", "description" )
class BikeForm(ModelForm):
name_bike = forms.CharField(required=True)
model = forms.CharField(required=True)
class Meta(CompanyForm.Meta):
model = Company
#transaction.atomic
def save(self):
company = super().save(commit=False)
company.name = self.cleaned_data.get("name")
company.description = self.cleaned_data.get("description")
company.save()
bike = Bike.objects.create(to_company=company)
bike.name_bike = self.cleaned_data.get("name_bike")
bike.model = self.cleaned_data.get("model")
bike.save()
return company
the relationship is kept in this line:
Bike.objects.create(to_company=company)
here is an example of a different type of user model
models user and type
https://gist.github.com/josuedjh3/259b4b3b03ab195637fe2db3c701edd6
FormModel the User and UserCreationForm
https://gist.github.com/josuedjh3/0c26d989552a82d5b252c5bd3fed1054
I have two models in Django that are related with a OneToOneField (PrinterProfile and PrinterAdress).
I am trying to do a form with PrinterProfileForm, but for some reason it does NOT pass the PrinterAddress fields into the form (it's not rendered by Django "magic" in the template).
What should I do so that my PrinterProfileForm include as well the fields from PrinterAddress (its related OneToOneField)?
Thanks a lot
class PrinterProfile(TimeStampedModel):
user = models.OneToOneField(User)
phone_number = models.CharField(max_length=120, null=False, blank=False)
additional_notes = models.TextField()
delivery = models.BooleanField(default=False)
pickup = models.BooleanField(default=True)
# The main address of the profile, it will be where are located all the printers.
class PrinterAddress(TimeStampedModel):
printer_profile = models.OneToOneField(PrinterProfile)
formatted_address = models.CharField(max_length=200, null=True)
latitude = models.DecimalField(max_digits=25, decimal_places=20) # NEED TO CHECK HERE THE PRECISION NEEDED.
longitude = models.DecimalField(max_digits=25, decimal_places=20) # NEED TO CHECK HERE THE PRECISION NEEDED.
point = models.PointField(srid=4326)
def __unicode__(self, ):
return self.user.username
class PrinterProfileForm(forms.ModelForm):
class Meta:
model = PrinterProfile
exclude = ['user']
You have to create second form for PrinterAddress and handle both forms in you view:
if all((profile_form.is_valid(), address_form.is_valid())):
profile = profile_form.save()
address = address_form.save(commit=False)
address.printer_profile = profile
address.save()
Of course in the template you need to show both forms under one <form> tag :-)
<form action="" method="post">
{% csrf_token %}
{{ profile_form }}
{{ address_form }}
</form>
Complementing the accepted answer:
If you have custom clean methods, you need to add a try/except case. For the example presented if address had a clean() method to validate something you needed to change it to:
def clean(self):
try:
printer_profile = self.printer_profile
except ObjectDoesNotExist:
pass
else:
...code to validate address...