Access fields in Django intermediate model of Groups - python

I'm creating a model of Group with Person and Membership, all like described in Django docs.
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
leader = models.BooleanField(default=False)
group = models.ForeignKey(Group)
I'm just making this all in views, becouse i want to make this "creating groups" as a function in my web app.
views.py:
#login_required
def groups(request):
user = request.user
if request.method == "POST":
form = GroupForm(request.POST)
if form.is_valid():
formm = form.save(commit=False)
g = Group.objects.create(name = formm.name)
p = Person.objects.create(name=request.user)
m = Membership.objects.create(person=p, group=g, leader=True)
gr = Group.objects.all()
per = Person.objects.all()
mem = Membership.objects.all()
context = {
'gr': gr,
'per':per,
'mem':mem,
'form': form,
}
return render(request, 'groups.html', context )
else:
gr = Group.objects.all()
per = Person.objects.all()
mem = Membership.objects.all()
form = GroupForm()
context = {
'gr': gr,
'per':per,
'mem':mem,
'form': form,
}
return render(request, 'groups.html', context)
groups.html:
{% block profile %}
<div class="jumbotron">
<h4>Create a new group here:</h4>
<form method="POST" class="post-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Go!</button>
</form>
</div>
{% for m in mem %}
<div class="jumbotron">
<br>
<p><b>Name of group:</b> {{ m.group }} </p>
<p><b>Member:</b> {{ m.person }} </p>
<br>
</div>
{% endfor %}
{% endblock %}
The problem is when I want to use
Membership.objects.filter(person=request.user)
and get error:
ValueError at /groups/
Cannot query "damian": Must be "Person" instance.
where damian is the name of the request.user
I just want to filter this to show in template only chosen memberships, groups etc...
Any one know how to fix this?

Do like this,
Membership.objects.filter(person__name=request.user)
or
p = Person.objects.get(name=request.user)
Membership.objects.filter(person=p)
You must pass a Person class instance to person argument in Membership filter query.

Related

Django create parent model and child model in one form

I am learning Python and Django, and I am trying to create a recipe with a form. The problem is I have two models, Recipe and RecipeIngredient and I don't know how to add recipe ingredients to the form since it requires the primary key of the recipe and from what I understand, the key is not created until after the form is saved? So how can I create a recipe with both the Recipe and RecipeIngredient when Recipe is not yet initialized?
Models.py:
class Recipe(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(upload_to='image/', blank=True, null=True)
name = models.CharField(max_length=220) # Lasanga
description = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True)
cookTime = models.CharField(max_length=50, blank=True, null=True)
timeStamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
#property
def title(self):
return self.name
def get_absolute_url(self):
return reverse("recipes:detail", kwargs={"id": self.id}) # recipes is from url.py app_name
def get_hx_url(self):
return reverse("recipes:hx-detail", kwargs={"id": self.id}) # recipes is from url.py app_name
def get_edit_url(self):
return reverse("recipes:update", kwargs={"id": self.id})
def get_image_upload_url(self):
return reverse("recipes:recipe-ingredient-image-upload", kwargs={"parent_id": self.id})
def get_delete_url(self):
return reverse("recipes:delete", kwargs={"id": self.id})
def get_ingredients_children(self):
return self.recipeingredient_set.all()
def get_instruction_children(self):
return self.recipeinstruction_set.all()
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=220) # grilled chicken pasta
description = models.TextField(blank=True, null=True)
quantity = models.CharField(max_length=50, blank=True, null=True)
unit = models.CharField(max_length=50, validators=[validate_unit_of_measure], blank=True, null=True)
instructions = models.TextField(blank=True, null=True)
timeStamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
def get_absolute_url(self):
return self.recipe.get_absolute_url() # recipe cannot be none
def get_hx_edit_url(self):
kwargs = {
"parent_id": self.recipe.id,
"id": self.id
}
return reverse("recipes:hx-ingredient-detail", kwargs=kwargs)
def get_delete_url(self):
kwargs = {
"parent_id": self.recipe.id,
"id": self.id
}
return reverse("recipes:ingredient-delete", kwargs=kwargs)
Views.py
#login_required
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
context = {
"form": form,
}
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
if request.htmx: # necessary to pass headers from htmx response if we want the django to recognise the htmx change
headers = {
"HX-Redirect": obj.get_absolute_url()
}
return HttpResponse("Created", headers=headers)
# if request.htmx: # could use this but the url doesn't update, stays as create, would need to use HX-Push header & HttpResponse + context somehow
# context = {
# "object": obj
# }
# return render(request, "recipes/partials/detail.html", context)
return redirect(obj.get_absolute_url())
return render(request, "recipes/create.html", context)
#login_required
def recipe_ingredient_update_hx_view(request, parent_id=None, id=None): # this is both create & edit, can create
if not request.htmx:
raise Http404
try:
parent_obj = Recipe.objects.get(id=parent_id, user=request.user)
except:
parent_obj = None
if parent_obj is None:
return HttpResponse("Not Found.")
instance = None
if id is not None:
try:
instance = RecipeIngredient.objects.get(recipe=parent_obj, id=id) # think of this as an object if that helps
except:
instance = None
form = RecipeIngredientForm(request.POST or None, instance=instance)
url = reverse("recipes:hx-ingredient-create", kwargs={"parent_id": parent_obj.id})
if instance:
url = instance.get_hx_edit_url()
context = {
"url": url,
"form": form,
"object": instance
}
if form.is_valid():
new_obj = form.save(commit=False)
if instance is None:
new_obj.recipe = parent_obj
new_obj.save()
context['object'] = new_obj # because it's possible the object/instance in None
return render(request, "recipes/partials/ingredient-inline.html", context)
return render(request, "recipes/partials/ingredient-form.html", context)
forms.py
from django import forms
from .models import Recipe, RecipeIngredient
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ['name', 'image', 'description', 'notes', 'cookTime']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
new_data = {
"placeholder": f"Recipe {str(field)}",
"class": "form-control",
}
self.fields[str(field)].widget.attrs.update(new_data)
class RecipeIngredientForm(forms.ModelForm):
class Meta:
model = RecipeIngredient
fields = ['name', 'quantity', 'unit']
urls.py
from django.urls import path
from .views import (
recipe_list_view,
recipe_delete_view,
recipe_create_view,
recipe_update_view,
recipe_detail_hx_view,
recipe_ingredient_update_hx_view,
recipe_ingredient_delete_view,
recipe_ingredient_image_upload_view,
recipe_ingredient_url_scrape_view
)
app_name='recipes' # allows use of recipes:list as a reverse url call
urlpatterns = [
path('', recipe_list_view, name='list'), # index / home / root
path('create/>', recipe_create_view, name='create'),
path('hx/<int:parent_id>/ingredient/<int:id>', recipe_ingredient_update_hx_view, name='hx-ingredient-detail'), #or detail
path('hx/<int:parent_id>/ingredient/', recipe_ingredient_update_hx_view, name='hx-ingredient-create'),
]
create.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div class="container-fluid px-5">
<h1 class="pb-5">Create Recipe</h1>
<div id="recipe-container">
<form action='.' method="POST" hx-post='.'>
{% csrf_token %}
<div class='row'>
<div class="row d-flex pb-5">
<div class="col-12 col-lg-6 justify-content-center d-flex order-first order-lg-last pictureBox"
style="height: 400px; width:450; border: solid tomato 1px;">
<div class="align-self-center">
{{ form.image|as_crispy_field }}
</div>
</div>
<div class="col-12 col-lg-6 order-lg-first">
<div class="pb-3">{{ form.name|as_crispy_field }}</div>
<div class="pb-3">{{ form.description|as_crispy_field }}</div>
</div>
</div>
<div class="col-12 col-md-6">
{{ form.notes|as_crispy_field }}
</div>
<div class="col-12 col-md-6">
{{ form.cookTime |as_crispy_field }}
</div>
</div>
</div>
<div class='col-12 col-md-4'>
<!-- ADD INGREDIENTS ? -->
</div>
<div class="htmx-indicator">Loading...</div>
<div class="d-flex">
<button class="btn btn-success htmx-inverted-indicator" style='margin-top:10px;' type='submit'>Save</button>
<a class="btn btn-danger" href='{% url "recipes:list" %}'>Delete</a>
</div>
{% if message %}
<p>{{ message }}</p>
{% endif %}
</form>
</div>
</div>
{% endblock content %}
ingredient-form.html
<form action='.' method="POST" hx-post='{% if url %} {{ url }} {% else %} . {{% endif %}' hx-swap='outerHTML'>
{% csrf_token %}
{{ form.as_p}}
<div class="htmx-indicator">Loading...</div>
<button class="htmx-inverted-indicator" style='margin-top:10px;' type='submit' >Save</button>
</form>
ingredient-inline.html
<div class="py-1" id="ingredient-{{object.id}}">
<p>{% if not edit %} <b>{{ object.quantity }} {% if object.unit %} {{ object.unit }} {% endif %}</b> {% else %} {{ object.quantity }} {{ object.unit }} {% endif %} - {{ object.name }}</p>
{% if edit %}
<button class="btn btn-primary" hx-trigger='click' hx-get='{{ object.get_hx_edit_url }}' hx-target="#ingredient-{{object.id}}">Edit</button> <!-- target will replace whole div-->
<button class="btn btn-danger" href='{{ object.get_delete_url }}' hx-post='{{ object.get_delete_url }}' hx-confirm="Are you sure you want to delete {{ object.name }}?" hx-trigger='click' hx-target="#ingredient-{{object.id}}" hx-swap="outerHTML">Delete</button>
{% endif %}
</div>
The key to this problem is using a formset, as you will likely want to save multiple ingredients to the recipe. Django documentation outlines how to use them. Your view would end up looking something like below, allowing you to save the parent model, which will give you the parent instance/primary key to then save the ingredients.
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
RecipeIngredientFormset = formset_factory(RecipeIngredientForm)
formset = RecipeIngredientFormset(request.POST or None)
context = {
"form": form,
"formset": formset,
}
if request.method == "POST":
if form.is_valid() and formset.is_valid():
parent = form.save(commit=False)
parent.user = request.user
parent.save()
#recipe ingredients
for form in formset:
child = form.save(commit=False)
child.recipe = parent
child.save()

Django forms with variable user entries

I want to create a django form that captures user entry such as name, address, age. For this type of information I can create a model such as
class GeneralUserInfo(models.Model):
firstname = models.CharField()
lastname = models.CharField()
address = models.CharField()
# etc....
However, I also want to capture maybe some information like their class schedule or family information.
class UserSchedule(models.Model):
course_number = model.IntegerField()
course_name = model.CharField()
# etc....
class FamilyInfo(models.Model):
family_member_type = models.CharField(choices = MEMBER_CHOICES) # mother, father, sibling
family_member_name = models.CharField() # jon doe
# etc....
where by each user, the number of courses and number of family members could vary.
I would like the form to look something like below
with a simple submit button to send things off to be saved.
My question is, how should I structure the form template considering there are multiple models?
The answer to the above question can take a couple of forms. I'll rewrite the example above to add more context.
Say there's a student, Ashley, with college course work (say she's taking 4 course) and with family (mom, pop, sis, bro). I'd like to capture all this information for Ashley. So I've written the following models.py
class Student(models.Model):
firstname = models.CharField()
lastname = models.CharField()
address = models.CharField()
# etc....
class Course(models.Model):
user = models.ForeignKey(GeneralUserInfo, on_delete = models.CASCADE)
course_number = model.IntegerField()
course_name = model.CharField()
# etc....
class Family(models.Model):
user = models.ForeignKey(GeneralUserInfo, on_delete = models.CASCADE)
family_member_type = models.CharField(choices = MEMBER_CHOICES) # mother, father, sibling
family_member_name = models.CharField() # jon doe
# etc....
Next, what is needed is to use inlineformset_factory inside your views.
You could potentially have two views in views.py. One that creates the student, and another that edits the information belonging to that student.
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import StudentForm
from django.forms import inlineformset_factory
from .models import Student, Course, Family
#login_required
def createstudent(request):
context = {'studentform': StudentForm}
if request.method == "POST":
form = StudentForm(request.POST)
if form.is_valid():
instance = form.save(commit = False) # https://www.youtube.com/watch?v=2h57cqFRcqg
instance.user = request.user
instance.save()
messages.success(request, "Saved new contact!")
return redirect('home')
return render(request, 'mainapp/createstudent.html', context)
#login_required
def editview(request, id):
student = Student.objects.get(pk = id)
CourseFormSet = inlineformset_factory(Student, Course, fields = ('name', 'skill'), extra=5, max_num=5)
FamilyFormSet = inlineformset_factory(Student, Family, fields = ('name', 'skill'), extra=5, max_num=5)
if request.method == "POST":
courseformset = CourseFormSet(request.POST, instance = contact)
Familyform = FamilyFormSet(request.POST, instance = contact)
if courseformset.is_valid():
courseformset.save()
if Familyform.is_valid():
Familyform.save()
if courseformset.is_valid() or Familyform.is_valid():
messages.success(request, "Saved new information")
return redirect('editview', id=id)
courseformset = CourseFormSet(instance = contact)
Familyform = FamilyFormSet(instance = contact)
context = {'courseformset': courseformset, 'title': 'Edit View', 'Familyform': Familyform}
return render(request, 'mainapp/editview.html', context)
Then inside an edit view template, editview.html
{% extends "mainapp/base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<br>
<h5>Edit the form below</h5>
<br>
<form method = "POST">
<br>
<h2>Courses</h2>
<br>
{% csrf_token %}
{{ courseformset.management_form }}
{% for form in courseformset %}
<article class="media content-section">
<div class="media-body">
{{ form.as_p }}
</div>
</article>
{% endfor %}
<br>
<h2>Family</h2>
<br>
{{ Familyform.management_form }}
{% for form in Familyform %}
<article class="media content-section">
<div class="media-body">
{{ form.as_p }}
</div>
</article>
{% endfor %}
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
{% endblock content %}
This is just an example. However, for a whole project example, please see: https://github.com/Johnnyboycurtis/onlineforms-project/

MultiValueDictKeyError when I trying to post image

When I trying to add image from admin panel all OK, but when I trying to add image from site, I have this error: image of error. When I trying to post Detail without image, I have the same problem. Before this wasn't.
views.py:
def new_detail(request):
if request.user.is_authenticated:
if request.user.is_superuser:
if request.method == 'POST':
car = request.POST['car']
author = request.user
detail = request.POST['detail']
price = request.POST['price']
description = request.POST['description']
image = request.FILES['images']
detail = Detail(car = car, author = author, detail = detail, price = price, description = description, images = image)
detail.save()
return redirect('/new_detail/')
else:
return redirect('/login/')
return render(request, 'shop/new_detail.html')
new_detail.html:
{% extends 'base.html' %}
{% block content %}
<div class="content container">
<div class="row">
<div class="col-md-8">
<div class=".signin">
<form action="" method="POST">
{% csrf_token %}
<h3>Автомобіль: </h3>
<select name="car">
<option selected>Audi A8 D2 3.3 TDI</option>
<option>Audi A8 D2 3.7</option>
...
...
...
<h3>Ціна: </h3><textarea name="price"></textarea>
<h3>Фотки: </h3><input type="image" name="images" />
<p>
<input type="submit" value="Опублікувати" />
</form>
</div>
</div>
</div>
models.py:
from django.db import models
class Detail(models.Model):
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,)
car = models.CharField(max_length=100)
detail = models.TextField()
description = models.TextField()
price = models.CharField(max_length=30)
images = models.ImageField(upload_to='details', null = True, blank = True)
def __unicode__(self):
return self.detail
def __str__(self):
return self.detail
The first problem is that you are missing enctype="multipart/form-data" from your form tag in the template. See the docs on file uploads for more info.
<form action="" method="POST" enctype="multipart/form-data">
Secondly, your view doesn't handle the case when data is missing from the form. Instead of doing request.POST['detail'] you should be checking if 'detail' in request.POST or using request.POST.get('detail').
However it would be very time consuming to check every field individually. You should look at Django forms and model forms, which can handle a lot of this for you.
from django import forms
class DetailForm(forms.ModelForm):
class Meta:
model = Detail
fields = ['car', 'author', 'detail', 'price', 'description', 'images']
Then your view will be something like
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: u.is_superuser)
def new_detail(request):
if request.method == 'POST':
form = DetailForm(request.POST)
if form.is_valid():
detail = form.save()
return redirect('/new_detail/')
else:
form = DetailForm(request.POST)
return render(request, 'shop/new_detail.html', {'form': form})
You can use the form to simplify your template as well:
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
</form>
See the docs on rendering fields manually if you need more control in the template.

Django - How to filter dropdown based on user id?

I'm not sure how to filter dropdown based on user id.
Not I want for user id 2.
I want exactly like this for user id 2.
Model
#python_2_unicode_compatible # only if you need to support Python 2
class PredefinedMessage(models.Model):
user = models.ForeignKey(User)
list_name = models.CharField(max_length=50)
list_description = models.CharField(max_length=50)
def __str__(self):
return self.list_name
class PredefinedMessageDetail(models.Model):
predefined_message_detail = models.ForeignKey(PredefinedMessage)
message = models.CharField(max_length=5000)
View
class PredefinedMessageDetailForm(ModelForm):
class Meta:
model = PredefinedMessageDetail
fields = ['predefined_message_detail', 'message']
exclude = ('user',)
def predefined_message_detail_update(request, pk, template_name='predefined-message/predefined_message_detail_form.html'):
if not request.user.is_authenticated():
return redirect('home')
predefined_message_detail = get_object_or_404(PredefinedMessageDetail, pk=pk)
form = PredefinedMessageDetailForm(request.POST or None, instance=predefined_message_detail)
if form.is_valid():
form.save()
return redirect('predefined_message_list')
return render(request, template_name, {'form':form})
html file
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
You can do it in view itself using
form = PredefinedMessageDetailForm(request.POST or None, instance=predefined_message_detail)
form.fields["predefined_message_detail"].queryset= PredefinedMessage.objects.filter(user=request.user)
But filtering happens based on request.user so it should be logged in.Consider that also. Hope this helps

Adding users to the specific group in Django

I'm trying to add some users, in my Django project, to my created before group, but I know how to add to the specific group which I know the name and it's not dynamic. I would like that when I select a specific group, already add the next user. The problem is here:
g = Group.objects.get(name= 'New')
What should I do to replace this and indicate this group which I clicked?
views.py:
#login_required
def choose_group(request, pk):
if request.method == "POST":
cvs = get_object_or_404(Cv, pk=pk)
p = Person.objects.create(name=cvs.author)
g = Group.objects.get(name= 'New')
m = Membership.objects.create(person=p, group=g, leader=False)
return redirect( 'proj.views.cv_detail', pk=cvs.pk )
else:
cv = Cv.objects.filter(author = request.user)
cvs = get_object_or_404(Cv, pk=pk)
mem = Membership.objects.all()
context = {
'mem':mem,
'cvs':cvs,
'cv':cv
}
return render(request, 'choose_group.html', context)
models.py:
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
leader = models.BooleanField(default=False)
group = models.ForeignKey(Group)
choose_groups.html:
{% block profile %}
<div class="jumbotron">
<h3>Choose a group to add:</h3>
</div>
<ul>
{% for m in mem %}
<form method="POST" class="post-form" >{% csrf_token %}
<li><button type="submit" class="li1"> <b>{{ m.group }}</b></li>
</form>
{% endfor %}
</ul>
{% endblock %}
The main issue i see here is that you are not sending the group name m.group from your html.
to do so, you must add name and value to the button element in your html (and don't forget to close the button element)
<button type="submit" class="li1" name="group" value="{{m.group}}"> <b>{{ m.group }}</b></button>
Then, you can retrieve this group value in your views.py by using request.POST
g = Group.objects.get(name=request.POST['group'])

Categories

Resources