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).
Related
I have 2 groups of 5 employees. In each group, one of the employees is that group's supervisor.
In the create view, once the supervisor field is populated, I would like the employee foreign key field to show only those employees belonging to that supervisor.
It would be nice to have the appropriate employees displayed based on the user (supervisor) without the supervisor field having to be populated first.
I have tried model forms to try to appropriately modify the employee foreign key field query set, obviously to no avail. Please help!
The code is as follows:
class Instruction(models.Model):
supervisor = models.ForeignKey(
Profile, on_delete=models.CASCADE,
related_name="supervisorinstructions"
)
employee = models.ForeignKey(
Profile, on_delete=models.CASCADE,
related_name="employeeinstructions"
)
instruction = models.CharField(max_length=300)
def __str__(self):
return f"{self.instruction}"
def get_absolute_url(self):
return reverse("myapp:instructiondetail", kwargs={"pk": self.pk})
class InstructionCreate(CreateView):
model = models.Instruction
fields = [
"supervisor",
"employee",
"instruction",
]
template_name = "myapp/instruction_create_form.html"
More information.
For more context (pardon the pun)...
I have already been able to get a list of employees reporting to a particular supervisor.
Each employee reports to only one supervisor, so none of these employees would appear in any other list of employees reporting to another supervisor.
From this, I have been able to put a link to a (template) view where the context is a chosen employee and use the context to produce a report specific to that employee as follows:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
profile = models.Profile.objects.get(**kwargs)
context["profile"] = profile
employee = profile.employee
context["employee"] = employee
supervisor = profile.supervisor
context["supervisor"] = supervisor
# report queries
return context
Since only that supervisor can generate that report, it should also be possible for only that supervisor to be able to create an instruction for the employee, without even having to specify in the form who (the supervisor) is creating the instruction for who (the employee), and for the appropriate attributes required by my model to be set automatically from the context data.
This is where I have a problem. I am using CBVs and only a supervisor
can create an object for an employee.
When the supervisor wants to create an instruction for the employee, I want to be able to use the context (as done in the case of the report) to fix who the instruction is for and who is giving the instruction.
At the moment, when the create form opens, regardless of whether the supervisor is identified, all the employees appear, making it possible for a supervisor to create instructions for employees of the other supervisor and that's what I am trying to prevent.
I am too new in Django to figure this out on my own. In my novice opinion, it should be possible to prepopulate the create form with the supervisor and employee details from the context which negates the drop-down problem.
New, the employee model as requested ---`
class Profile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name="userprofiles")
employee = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True,
related_name="employeeprofiles",
)
supervisor1 = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True,
related_name="supervisorprofiles",
)
def __str__(self):
return f"{self.user}"
def get_absolute_url(self):
return reverse("myapp:profiledetail", kwargs={"pk": self.pk})`
Corrected the name of the last model, was Employee but should have been Profile as indicated in the Instruction model.
Hope this is the answer:
class InstructionCreateForm(ModelForm):
class Meta:
model = models.Instruction
fields = ["supervisor", "employee", "instruction"]
def clean(self):
cleaned_data = super(InstructionCreateForm, self).clean()
supervisor = cleaned_data.get('supervisor')
employee = cleaned_data.get('employee')
# check if the supervisor is the employee's supervisor and raise error if not
if supervisor != models.Profile.objects.filter(employee=employee).supervisor:
self.add_error(None, ValidationError('The employee is not your direct subordinate.'))
return cleaned_data
class InstructionCreate(CreateView):
model = models.Instruction
template_name = "internalcontrol/instruction_create_form.html"
form_class = forms.InstructionCreateForm
It worked! But only after this change:
I replaced if supervisor != models.Profile.objects.filter(employee=employee).supervisor:
with if employee != models.Profile.objects.filter(employee=employee, supervisor=supervisor):
for it to work.
The CBV create template:
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="btn-group">
<input type="submit" class="btn btn-primary btn-sm" value="Add">
List
</div>
</form>
The problem was to prevent a supervisor from giving instructions to someone else's subordinates.
Because I am new in Django, I focused on methodology I am not yet good enough in and forgot to focus on the desired outcome.
Using the code below achieves the objective in the first sentence:
class Instruction(models.Model):
employee = models.OneToOneField(Profile, on_delete=models.CASCADE, blank=True, null=True,
related_name="employeeinstructions")
supervisor = models.ForeignKey(Profile, on_delete=models.CASCADE, blank=True, null=True,
related_name="supervisorinstructions")
class InstructionCreateForm(ModelForm):
class Meta:
model = models.Instruction
fields = ["employee", "instruction"]
def clean(self):
cleaned_data = super(InstructionCreateForm, self).clean()
supervisor = cleaned_data.get('supervisor')
employee = cleaned_data.get('employee')
if supervisor != models.Profile.objects.filter(employee=employee).supervisor:
self.add_error(None, ValidationError('The employee is not a subordinate of this supervisor.'))
return cleaned_data
class InstructionCreate(CreateView):
model = models.Instruction
template_name = "internalcontrol/instruction_create_form.html"
form_class = forms.InstructionCreateForm
def form_valid(self, form):
user = self.request.user.id
profile = models.Profile.objects.get(user=user, employee=True)
form.instance.supervisor = profile
return super().form_valid(form)
The CBV create template is:
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="btn-group">
<input type="submit" class="btn btn-primary btn-sm" value="Add">
List
</div>
</form>
The correct answer to my original question - the first prize I was looking for - is in Django docs (https://docs.djangoproject.com/en/3.2/ref/forms/fields/#fields-which-handle-relationships) and in Django-Filter (https://django-filter.readthedocs.io/en/stable/guide/usage.html#filtering-the-related-queryset-for-modelchoicefilter)
That closes this chapter.
Until you update your question, I will assume you have a model similar to this:
class Employee(models.Model):
# some fields not related to the current question
supervisor = models.ForeignKey(
'Employee', on_delete=models.CASCADE,
related_name="direct_reports"
)
Now if you have an Employee object named supervisor, you can do supervisor.direct_reports to get a queryset of Employee objects who's supervisor is supervisor. For your purposes, you probably want to send this in the context of your form in order to populate a dropdown list.
Alternatively, only pass the supervisor in the context and then access supervisor.direct_reports directly in the template.
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.
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
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...
I have an app that let users create blogs and allow other users to comment on each other blogs.The Problem is .In order to create a comment object , I require the blog id and text . I can grab the text data via post but I'm having trouble getting the blog id from POST and the only way I can think of getting it is via value field in the form
How can I grab the value field from POST?
My models
class Blog(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField()
class BlogComment(models.Model):
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User)
body = models.TextField()
blog = models.ForeignKey(Blog)
my forms.py
class BlogCommentForm(forms.ModelForm):
text = forms.CharField(required=False)
class Meta:
model = BlogComment
fields = ()
<form method ="POST"> {% csrf_token %}
<input type = "hidden" name="d" value= "blog.id" />
{{form}}
</form>
My views
def Blogs(request,blog_id):
form = BlogCommentForm(request.POST)
if request.method == "POST":
if form.is_valid():
text = form.cleaned_data['text']
value = form.cleaned_data['value']
form = BlogCommentForm()
blog.objects.get(pk=blog_id)
comment = BlogComment.objects.filter(blog=blog)
return render(request,'blogcomment.html',{'comment':comment,'form':form})
request.POST['d']
or to avoid raising an Exception if it's not there use
request.POST.get('d', False)
You can always get the blog id from ?= parameter in url.
When user goes to comment someone's blog the url might be http://yoursite.com/blog/id/comment or http://yoursite.com/blog/comment?blogid=12345.