ModelForm with OneToOneField in Django - django

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...

Related

Django: Add a new value to ModelChoiceField

I have a ModelChoiceField in a form that uses a TextInput widget. I want to be able to select a value from the database or add new entries to the database with this input. If the value is not already in the database, I get an error on the form that says "Select a valid choice. That choice is not one of the available choices."
Model
class FeedCategory(models.Model):
category = models.CharField(max_length=255, unique=True)
class RssFeed(models.Model):
category = models.ForeignKey(FeedCategory, null=True, on_delete=models.SET_NULL)
name = models.CharField(max_length=255)
feed = models.URLField()
Form
class RssForm(forms.Form):
name = forms.CharField()
feed = forms.URLField()
category = forms.ModelChoiceField(queryset=FeedCategory.objects.all(), to_field_name='category', widget=forms.TextInput())
def clean(self):
cleaned_data = super().clean()
????
Views
class RssCreateView(FormView):
template_name = 'dashboard/rss_feed_form.html'
form_class = RssForm
success_url = '/dashboard/'
def form_valid(self, form):
name = form.cleaned_data['name']
feed = form.cleaned_data['feed']
category = form.cleaned_data['category']
rss_obj = RssFeed(category=category, name=name, feed=feed)
rss_obj.save()
return super().form_valid(form)
Template
<form method="post">
{%csrf_token%}
{{form|crispy}}
<button type="submit">Save</button>
</form>
It might help you what I am using:
category = models.ForeignKey("General.entity",verbose_name='Category', db_column="CategoryEntityRef", null=False, blank=False)
so, what I am doing with this is creating a field that points to an existing category that exists in another table. It will display it as a dropdown box. However using this method will allow me to have the option to add another Category:

Django forms - Allow a user to create a group for other users

I am trying to let a user create a "club" (basically a group) where the user later on can add users to.
Currently it does not create a field in the database somehow.
Any suggestions would be appreciated since I am fairly new to forms.
Model
class Club(models.Model):
owner = models.CharField(max_length=30)
topic = models.CharField(max_length=30)
start = models.DateTimeField(verbose_name='start date', auto_now_add=False)
end = models.DateTimeField(verbose_name='end date', auto_now_add=False)
account = models.ManyToManyField(Account)
Views
#login_required
def add_club(request):
if request.method == "POST":
form = AddClubForm(request.POST, instance=request.user)
print(form)
if form.is_valid():
form.save()
return HttpResponseRedirect(request.path_info)
else:
form = AddClubForm(instance=request.user)
return render(request, 'page/club.html', {
"form": form,
})
Form
class AddClubForm(forms.Model):
owner = forms.CharField(required=True)
topic = forms.CharField(required=False)
start = forms.DateField(required=False)
end = forms.DateField(required=False)
class Meta:
model = Club
fields = (
'owner',
'topic',
'start',
'end',
)
Template
<form method="POST" action="">
{% csrf_token %}
<div class="col-md-6">
<label class="labels">Create a club</label>
{{ form.owner }}
<input class="btn" type="submit" value="Add club">
</div>
</form>
Since this answered your problem, I am posting the solution here:
You need to add blank=True, null=True to your fields in your model, otherwise it expects them when saving the form.
class Club(models.Model):
owner = models.CharField(max_length=30)
topic = models.CharField(max_length=30, blank=True, null=True)
start = models.DateTimeField(verbose_name='start date', auto_now_add=False, blank=True, null=True)
end = models.DateTimeField(verbose_name='end date', auto_now_add=False, blank=True, null=True)
account = models.ManyToManyField(Account, blank=True)
Concerning the instance=request.user, I believe you misunderstanding the use of instance in a ModelForm.
If the request method is GET, the instance is used to populate a ModelForm with data from an existing Club object, and then pass it to your template to display the information.
If the request method is POST (or PUT), instance represent the existing Club object you want to update with data received from the form.
You usually need to use the instance arg in a DetailView (either to update or retrieve one specific Club), never when creating an object.
That's why you need to remove the instance arg in your views.py:
form = AddClubForm(request.POST)

Django form to formset

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.

Django: How to get User model's fields instead of select list in Employee form?

I'm working on an application and I'm stuck in the very beginning. Please help me out here.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=False)
designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=False)
User is from django.contrib.auth.models
views.py
class EmployeeCreateView(LoginRequiredMixin, generic.CreateView):
model = Employee
fields = '__all__'
success_url = reverse_lazy('core:view-employees')
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save(commit=False)
self.object.company = self.request.user.company
self.object.save()
form.save_m2m()
return super().form_valid(form)
employee_form.html
<form action="{% url 'core:add-employees' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" class="bnt btn-small">
</form>
rendered html form
Here's the problem.
Instead of User being a select list, I want fields of User model, i.e. username, email, password etc, while the Designation should remain as the select list.
Thanks in advance.
You can use ModelChoiceField for the user field in your form. Creating a form class would make it rather easier.
See https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield for reference.
there are many ways to this. If you are only using designation field as an extra field to User. just adding designation to the User model will work easily.
to do this you can use multi-table inheritance as Following.
class Employee(User): # change Here
# Remove -> user = models.OneToOneField(User, on_delete=models.CASCADE, null=False)
designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=False)
Now you will get what you wanted. but note that this will not best use case for many situations as it creates a left outer join in DB table.
but for this situation, I don't think it will affect anything.
to know more about Django multi-table inheritance see: https://docs.djangoproject.com/en/2.1/topics/db/models/#multi-table-inheritance
for other ways to extend user model see: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html

Django add textfield on click

I am writing a django recipe website and have a question about JSON Field and forms
I am trying to write the create recipe function for the site and wanted to do two things:
I want to add text fields on mouse click similarly to adding attachments with e-mails. I want to use JSONField to do so (unless picklefield is better)
i want the user to be able to edit the recipe in one textfield.
I was hoping i could pack all of the steps into one text field and allow them to edit that field and then unpack them back into the steps. otherwise it might get confusing for the user to have to edit each individual step.
here are my models from the django project:
class Cookbook(models.Model):
def __unicode__(self):
return self.name
name = models.CharField(max_length=50)
pub_date = models.DateTimeField('date published')
user = models.ForeignKey(User, related_name='cookbooks')
recipes = models.ManyToManyField('Recipe', related_name = 'cookbooks')
class Recipe(models.Model):
def __unicode__(self):
return self.name
original_cookbook = models.ForeignKey(Cookbook)
name = models.CharField(max_length=200)
author = models.CharField(max_length= 100)
picture = models.ImageField(upload_to = 'Downloads', blank=True)
pub_date = models.DateTimeField('date published', auto_now_add=True, blank=True)
ingredients = JSONField()
steps = JSONField()
prep_time = models.IntegerField()
Here is the view in which I create a new recipe. Right now I am unsure how to use the JSONField in my view.
I found this link but it states "Finally, I'm not sure how to interact with forms yet, so that realm is a bit murky." Seeing that I am using a form, has this been resolved?
def createrecipe(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/index/')
else:
if request.method == 'POST':
form = RecipeForm(request.POST)
if form.is_valid():
recipe = form.save(commit=False)
recipe.original_cookbook = request.user.cookbooks.all()[0]
recipe.pub_date = datetime.datetime.now()
recipe.save()
user = request.user
cookbooks = user.cookbooks
cookbook = cookbooks.all()[0]
cookbook.recipes.add(recipe)
return HttpResponseRedirect('/account')
else:
form = RecipeForm()
return render_to_response('cookbook/createrecipe.html',
{'form':form},
context_instance=RequestContext(request))
here is the createrecpe.html block content:
{% block content %}
<form action="." method="POST">
<table>
{% csrf_token %}
{{ form.as_table }}
</table>
<p><input type="submit" value="Submit"></p>
</form>
{% endblock %}
I am having a hard time bridging the gap between the JSONField model and the view to display/enter text into the JSON field. I also am confused how to display the jsonfield in a template.
thank you for any help this has really been discouraging me,
snackerfish
You can use a formset here. In your case - take django-jsonfield or django-picklefield to avoid manual converting to/from data on object's saving and retrieving, and create a formset providing empty list on init.
You can manipulate it on client side using js, but do not forget to increment forms count in TOTAL_FORMS hidden input. After POSTing the form and cleaning the data you'll have formset.cleaned_data() which you could put to your PickleField without any aditional processing (and data from the field can be put as initial to formset if you'll need to edit the recipe).