I created a filter to only show items with certain values.
A list of social measures is displayed. There are two questions about having children and unemployment. We have or don't have children, and we are or aren't unemployed.
The main problem is that the filter doesn't work correctly, due to the fact that I only add one option "no" to the entity.
For example, we have a social measure "child allowance", and it's paid regardless of income. I created this measure with having children "yes" and unemployment "no". And if the user selects having children "yes" and unemployment "yes", then the page won't show this measure, although the person is entitled to it. Because only "no" was introduced into unemployment. How to enter "no" and "yes" at the same time when creating a measure? And for the site to show this measure regardless of the choice of unemployment.
measure_list.html
<div class="headtext">
<h3>Choose your life features</h3>
</div>
<form action="{% url 'filter' %}" method="get" name="filter">
<h3>Do you have children?</h3>
<ul>
{% for measure in view.get_children %}
<li>
<input type="checkbox" class="checked" name="children" value="{{ measure.children }}">
<span>{{ measure.children }}</span>
</li>
{% endfor %}
</ul>
<h3>Are you unemployed?</h3>
<ul>
{% for measure in view.get_income %}
<li>
<input type="checkbox" class="checked" name="income" value="{{ measure.income }}">
<span> {{ measure.income }}</span>
</li>
{% endfor %}
</ul>
<button type="submit">Найти</button>
</form>
<h3>Measures:</h3>
{% for measure in object_list %}
<div> {{ measure.name }} </div>
{% endfor %}
Views.py
class FilterList:
def get_agestatus(self):
return Measure.objects.values("income").distinct()
def get_children(self):
return Measure.objects.values("children").distinct()
def get_numberchildren(self):
class MeasureView(FilterList, ListView):
model = Measure
template_name = 'measure_list.html'
class FilterMeasureView(FilterList, ListView):
template_name = 'measure_list.html'
def get_queryset(self):
queryset = Measure.objects.filter(
income__in=self.request.GET.getlist("income"),
children__in=self.request.GET.getlist("children"),
).distinct()
return queryset
Models.py
class Measure(models.Model):
name = models.CharField(max_length=255)
income = models.CharField(max_length=255)
children = models.CharField(max_length=255)
def __str__(self):
return self.name + ' | ' + str(self.agestatus)
def get_absolute_url(self):
return reverse('measure_list')
Forms.py
incomechoice = [('No', 'No'), ('Yes', 'Yes')]
childrenchoice = [('No', 'No'), ('Yes', 'Yes')]
class MeasureForm(forms.ModelForm):
class Meta:
model = Measure
fields = ('name', 'income', 'children')
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'income': forms.Select(choices=incomechoice,attrs={'class': 'form-control'}),
'children': forms.Select(choices=childrenchoice,attrs={'class': 'form-control'}),
}
Related
I would like to add information in a form, coming from the model linked with a M2M relationship to the model I'm updating.The form works properly, but I'm not able to add any information.
Here is what I get:
Here is the expected result:
My solution: finally, I updated __str__() mthod in UserGroup model to display what I expect (but, at this stage, I lost the dynamic part and my view does not work anymore :-/)
The main model is Event and it's linked to Groups thanks to this relationship; in the form, all groups are listed and displayed with checkboxes, but I'm only able to display the groups' name, no other information.
It looks like I miss some data / information: the group name is displayed only because I use {{grp}}} (see below) but it has no attribute / filed available, even if it is initialized with a query from the group model.
I envisaged a workaround (see below) because my first tries made me consider this kind of solution, but I'm not able to reproduce what I did :-/
Any idea of what I did wrong, or any advice to manage this? Thanks in advance.
Here are related code parts.
Models:
class UserGroup(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
users = models.ManyToManyField(UserComp, verbose_name="utilisateurs", blank=True)
group_name = models.CharField("nom", max_length=100)
weight = models.IntegerField("poids", default=0)
hidden = models.BooleanField(default=False)
def __str__(self):
return self.group_name
class Event(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
groups = models.ManyToManyField(UserGroup, verbose_name="groupes", blank=True)
rules = [("MAJ", "Majorité"), ("PROP", "Proportionnelle")]
event_name = models.CharField("nom", max_length=200)
event_date = models.DateField("date de l'événement")
slug = models.SlugField()
current = models.BooleanField("en cours", default=False)
quorum = models.IntegerField(default=33)
rule = models.CharField(
"mode de scrutin", max_length=5, choices=rules, default="MAJ"
)
class Meta:
verbose_name = "Evénement"
constraints = [
models.UniqueConstraint(fields=["company_id", "slug"], name="unique_event_slug")
]
def __str__(self):
return self.event_name
Form:
class EventDetail(forms.ModelForm):
groups = forms.ModelMultipleChoiceField(
label = "Liste des groupes",
queryset = None,
widget = forms.CheckboxSelectMultiple,
required = False
)
class Meta:
model = Event
fields = ['event_name', 'event_date', 'quorum', 'rule', 'groups']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
self.fields['groups'].queryset= UserGroup.objects.\
filter(company=instance.company).\
order_by('group_name')
View:
#user_passes_test(lambda u: u.is_superuser or (u.id is not None and u.usercomp.is_admin))
def adm_event_detail(request, comp_slug, evt_id=0):
'''
Manage events creation and options
'''
company = Company.get_company(request.session['comp_slug'])
# all_groups = list(UserGroup.objects.filter(company=company, hidden=False).order_by('group_name').values())
if evt_id > 0:
current_event = Event.objects.get(id=evt_id)
event_form = EventDetail(request.POST or None, instance=current_event)
else:
event_form = EventDetail(request.POST or None)
event_form.fields['groups'].queryset = UserGroup.objects.\
filter(company=company, hidden=False).\
order_by('group_name')
if request.method == 'POST':
if event_form.is_valid():
if evt_id == 0:
# Create new event
event_data = {
"company": company,
"groups": event_form.cleaned_data["groups"],
"event_name": event_form.cleaned_data["event_name"],
"event_date": event_form.cleaned_data["event_date"],
"quorum": event_form.cleaned_data["quorum"],
"rule":event_form.cleaned_data["rule"]
}
new_event = Event.create_event(event_data)
else:
new_event = event_form.save()
else:
print("****** FORMULAIRE NON VALIDE *******")
print(event_form.errors)
return render(request, "polls/adm_event_detail.html", locals())
HTML (I did not put each parts of the 'accordion' widget, I do not think they have anything to do with the problem):
{% if evt_id %}
<form action="{% url 'polls:adm_event_detail' company.comp_slug evt_id %}" method="post">
{% else %}
<form action="{% url 'polls:adm_create_event' company.comp_slug %}" method="post">
{% endif %}
{% csrf_token %}
<!-- Hidden field where the referer is identified to go back to the related page after validation -->
<input type="hidden" name="url_dest" value="{{ url_dest }}" />
<br>
<!-- Accordion -->
<div id="eventDetails" class="accordion shadow">
<div class="card">
<div class="card-header bg-white shadow-sm border-0">
<h6 class="mb-0 font-weight-bold">
Evénement
</h6>
</div>
<div class="card-body p-5">
<p>Nom : {{event_form.event_name}} </p>
<p>Date : {{event_form.event_date}} </p>
</div>
</div>
<!-- Accordion item 2 - Event's groups -->
<div class="card">
<div id="headingTwo" class="card-header bg-white shadow-sm border-0">
<h6 class="mb-0 font-weight-bold">
<a href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo" class="d-block position-relative collapsed text-dark collapsible-link py-2">
Groupes d'utilisateurs
</a>
</h6>
</div>
<div id="collapseTwo" aria-labelledby="headingTwo" data-parent="#eventDetails" class="collapse show">
<div class="card-body p-5">
<p>Sélectionnez le(s) groupe(s) d'utilisateurs participants à l'événement :</p>
<ul>
{% for grp in event_form.groups %}
<li>{{ grp }}
{{ grp.weight }}
{{ grp.hidden }}
{{ grp.nb_users }}
</li>
{% endfor %}
</ul>
<p></p>
</div>
</div>
</div>
</div> <!-- Accordion end -->
<button class="btn btn-success mt-5" type="submit">{% if evt_id %}Mettre à jour{% else %}Créer{% endif %}</button>
     
<a class="btn btn-secondary back_btn mt-5" href="*">Annuler</a>
<div class="row">
<div hidden>
<!-- List of groups in event -->
{{ event_form.group_list }}
</div>
</div>
</form>
Workaround
If it's not possible to achieve this directly, I thought to a workaround that would be implemented in several parts:
Create a list almost like the queryset: group_list = UserGroup.objects.filter(company=instance.company).order_by('group_name').values()
I already know I can display each group with its details and a checkbox
on client side (javascript), I manage an hidden list that would be part of the form, with the ID of each selected group. That means that the list will be dynamically updated when a box is checked on unchecked
on the POST request, read the list to update the group attribute of updated event.
I would have prefered the users actions having effect directly to the form, but I know this could work
You're accessing the form's groups field, not the model instance. The form field doesn't have any relationship to other models, it's just a field. You can access the underlying instance of the model form using form.instance.
Also note that you get a relationship manager object when querying related models. Hence, use .all to query all groups.
Try
<ul>
{% for grp in event_form.instance.groups.all %}
<li>{{ grp }}
{{ grp.weight }}
{{ grp.hidden }}
{{ grp.nb_users }}
</li>
{% endfor %}
</ul>
I have a list of categories as well as a list of products my template is in such a manner that it has category sections each with a display of products that belong to said categories. I created a for loop for categories so as to easily display category sections for each category I create. I then went on to create a forloop for products within the category forloop with a condition so as to match products with their actual category before they are displayed under their category section. how can I slice the resulting products to limit the number of products shown
Models.py
class Category(models.Model):
name = models.CharField(max_length=120)
image_263x629 = models.ImageField(upload_to='cat_imgs')
image_263x629_2 = models.ImageField(upload_to='cat_imgs')
image_263x629_3 = models.ImageField(upload_to='cat_imgs')
img_array = [image_263x629, image_263x629_2, image_263x629_3]
description = models.CharField(max_length=250)
def __str__(self):
return self.name
class SubCategory(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=300)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.name
#def get_absolute_url(self):
# return reverse('subcat_detail', args=[str(self.id)])
class Product(models.Model):
name = models.CharField(max_length=120)
price = models.FloatField()
image_182x182 = models.ImageField(upload_to='pdt_imgs/')
image_1200x1200 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
image_600x600 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
image_600x600_2 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
image_300x300 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
img_array = [image_1200x1200, image_600x600, image_600x600_2]
sku = models.IntegerField()
available = models.BooleanField(default=True)
discount = models.IntegerField(default = 0)
description = models.CharField(max_length=120, blank=True, null=True)
brand = models.CharField(max_length=120, blank=True, null=True)
category = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
seller = models.ForeignKey(Seller, on_delete=models.CASCADE)
Views
class HomePageView(ListView):
model = SubCategory
template_name = 'home.html'
queryset = SubCategory.objects.all()
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['products'] = Product.objects.all()
context['pdts'] = Product.objects.order_by('?')[:12]
context['categories'] = Category.objects.all()
context['subcategories'] = SubCategory.objects.all()
return context
Template
{% for category in categories %}
<div class="ps-block--products-of-category">
<div class="ps-block__categories">
<h3>{{ category.name }}</h3>
<ul>
{% for subcategory in subcategories %}
{% if subcategory.category.name == category.name %}
<li>{{ subcategory.name }}</li>
{% endif %}
{% endfor %}
</ul><a class="ps-block__more-link" href="{% url 'cat_detail' category.id %}">View All</a>
</div>
<div class="ps-block__slider">
<div class="ps-carousel--product-box owl-slider" data-owl-auto="true" data-owl-loop="true"
data-owl-speed="7000" data-owl-gap="0" data-owl-nav="true" data-owl-dots="true" data-owl-item="1"
data-owl-item-xs="1" data-owl-item-sm="1" data-owl-item-md="1" data-owl-item-lg="1" data-owl-duration="500"
data-owl-mousedrag="off">
<img src="{{ category.image_263x629.url }}" alt="">
<img src="{{ category.image_263x629_2.url }}" alt="">
<img src="{{ category.image_263x629_3.url }}" alt="">
</div>
</div>
<div class="ps-block__product-box">
{% for product in products %}
{% if product.category.category.name == category.name %}
<div class="ps-product ps-product--simple">
<div class="ps-product__thumbnail"><a href="{% url 'pdt_detail' product.id %}"><img src="{{ product.image_300x300.url }}"
alt=""></a>
{% if product.discount > 0 %}
<div class="ps-product__badge">-{{ product.discount }}%</div>
{% endif %}
{% if product.available == False %}
<div class="ps-product__badge out-stock">Out Of Stock</div>
{% endif %}
</div>
<div class="ps-product__container">
<div class="ps-product__content" data-mh="clothing"><a class="ps-product__title"
href="{% url 'pdt_detail' product.id %}">{{ product.name }}</a>
<div class="ps-product__rating">
<select class="ps-rating" data-read-only="true">
<option value="1">1</option>
<option value="1">2</option>
<option value="1">3</option>
<option value="1">4</option>
<option value="2">5</option>
</select><span>01</span>
</div>
<p class="ps-product__price sale">UGX{{ product.price }}</p>
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
Please do not filter in the template. You should filter in the view. A template implements rendering logic, not business logic*.
You can filter and slice in the view with:
def my_view(request):
# …
products = Product.objects.filter(category__name='specified category')[:10]
context = {
'products': products
}
return render(request, 'my_template.html', context)
This is not only the place where filtering belongs, it is also more efficient since we here will filter and slice on the database side. Typically a database can do this more efficient, and it furthermore limits the bandwidth from the database to the Django/Python layer.
Note (based on #SLDem's comment):
If you aim to filter children, you make use of a Prefetch object [Django-doc]. Indeed, imagine that we have a QuerySet of Categorys and we want to only retain Products that are available, we can use:
from django.db.models import Prefetch
categories = Category.objects.prefetch_related(
Prefetch(
'product_set',
Product.objects.filter(available=True),
to_attr='available_products'
)
)
then in the template we can render this with:
{% for category in categories %}
{% for product in category.available_products %}
…
{% endfor %}
{% endfor %}
For one of my open source projects, I need to create ONE add/edit page in order to make possible to edit several records with one save.
The repo is an IMDB clone formed for learning purpose. A user can add her/his favorite genres in her/his profile. Then an edit page is formed to show the list of those favored genres and the movies within that genre. (A for loop here) User can add notes, watch list options and so on to those movies. (NOT a FORMSET)
However, the code doesn't work as expected. The page cannot be saved and only the first checkbox of the list can be changed.
There is no error.
NOTE:
You can install repo with dummy data.
(https://github.com/pydatageek/imdb-clone)
Then after logging in, select your favorite genres. (http://localhost:8000/users/profile/)
Then (I wish it can be solved here) you can see the movies with your selected genres. Add notes, to watch list... (http://localhost:8080/users/profile/movies2/)
# users/templates/user-movies-with_loop.html
{% extends 'base.html' %}{% load crispy_forms_tags %}
<!-- Title -->
{% block htitle %}Your movies from favorite genres{% endblock %}
{% block title %}Your movies from favorite genres{% endblock %}
{% block content %}
<div class="card card-signin">
{% include 'users/profile-menu.html' %}
<h3 class="card-title text-center my-4">Take notes for your movies <small></small></h3>
<hr class="mb-1">
<div class="card-body">
<form method="POST">
{% csrf_token %}
{% for genre in user.genres.all %}
<h2 for="genre" name="genre" value="{{ genre.id }}">{{ genre.name }}</h2>
{% for movie in genre.movies.all %}
<div class="ml-5">
<h4>{{ movie.title }}</h4>
{{ form|crispy }}
</div>
<input type="hidden" name="user" value="{{ user.id }}">
<input type="hidden" name="movie" value="{{ movie.id }}">
{% empty %}
<p class="alert alert-danger">The genre you have selected on your profile doesn't have any movies!</p>
{% endfor %}
{% empty %}
<p class="alert alert-danger">You should select genres with movies from your profile to edit here!</p>
{% endfor %}
<input class="btn btn-lg btn-primary btn-block text-uppercase" type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}
# users.forms.py
...
class UserMovieFormWithLoop(ModelForm):
genre = forms.HiddenInput(attrs={'disabled': True})
class Meta:
model = UserMovie
fields = ('user', 'movie', 'note', 'watched', 'watch_list')
widgets = {
'user': forms.HiddenInput,
'movie': forms.HiddenInput,
'watched': forms.CheckboxInput(),
}
...
# users.models.py
...
class UserMovie(models.Model):
"""
Users have notes about their favorite movies.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
movie = models.ForeignKey('movies.Movie', default=1, on_delete=models.CASCADE)
note = models.CharField(max_length=250, null=True, blank=True)
watched = models.BooleanField(default=False, verbose_name='Have you seen before?')
watch_list = models.BooleanField(default=False, verbose_name='Add to Watch List?')
def __str__(self):
return f'{self.user.username} ({self.movie.title})'
...
# users.views.py
...
class UserMovieViewWithLoop(LoginRequiredMixin, CreateView):
model = UserMovie
template_name = 'users/user-movies-with_loop.html'
form_class = UserMovieFormWithLoop
success_message = 'your form has been submitted.'
success_url = reverse_lazy('users:user_movies2')
def form_valid(self, form):
user = self.request.user
movie_counter = Movie.objects.filter(genres__in=user.genres.all()).count()
f = form.save(commit=False)
f.user = user
for i in range(movie_counter):
f.pk = None
f.save()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super(UserMovieViewWithLoop, self).get_context_data(**kwargs)
context['form'] = self.form_class
return context
def get_object(self):
user = self.request.user
return UserMovie.objects.get(user=user)
...
I'm trying to show information from two models, in a single view in a Django project.
I have 2 models: Main (parent), Visits (child)
I would like to show a details view of Main (name, date of birth) and then show a list of the Visits. Effectively, show one record from parent table, and all the related children tables. But the children tables are only partially showing up (see the image).
Also, can someone tell me how the Django code knows to render only the child records that are associated with the parent record (where/when are foreign keys filtered?)
Image showing the problem
eg:
Main.name
Visit.date - Visit.type
Visit.date - Visit.type
Visit.date - Visit.type
views.py
class MainDetailView(generic.DetailView):
model = Main
template_name = "myapp/main-detail.html"
def get_context_data(self, **kwargs):
context = super(MainDetailView, self).get_context_data(**kwargs)
context['visit'] = Visit.objects.all()
# And so on for more models
return context
models.py
class Visit(models.Model):
fk_visit_patient = models.ForeignKey(Main, on_delete=models.CASCADE,
verbose_name=('Patient Name'))
visit_date = models.DateField()
visit_label = models.CharField(max_length=256, blank=True, null=True)
visit_specialty_list = (
(str(1), 'General Practice'),
(str(2), 'Internal Medicine'),
(str(3), 'Surgery'),
visit_specialty = models.CharField(
max_length=256,
choices=visit_specialty_list,
default=1, )
def __str__(self):
return str(self.visit_label)
def get_absolute_url(self):
return reverse('myapp:main-detail', kwargs={'pk': self.pk})
template.html
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-7">
<div class="'panel panel-default">
<div class="panel-body">
<h1>{{ main.name }}</h1>
<p><strong>Date of Birth:</strong> {{ main.date_of_birth }}</p>
<div style="margin-left:20px;margin-top:20px">
<h4>Visits</h4>
{% for visit in main.visit_set.all %}
<li>{{ Visit.visit_date }} - {{ Visit.visit_specialty }}</li>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
You loop defines the variable as visit, but inside you refer to Visit. You need to be consistent.
{% for visit in main.visit_set.all %}
<li>{{ visit.visit_date }} - {{ visit.visit_specialty }}</li>
{% endfor %}
Note, there is no need to pass the Visits separately to the template as you are doing in get_context_data - you should remove that method completely.
Considering a Django application, I have to apply I18N to the values stored in "Area" table. The values of the field i18n_code points to msgid entries in my locale/en_US/LC_MESSAGES/django.po.
As you can see in my code, Startup model has a ManyToMany field of Area. In my form, area is a CheckboxSelectMultiple widget. How can I, inside my form01.html (template) get the i18n_code, pass to {% trans area.i18n_code %} so I can get options rendered in proper language?
I checked many questions relative to this subject... but I did not got some clean solution.
Checkout the codes:
models.py:
#python_2_unicode_compatible
class Area (models.Model):
name = models.CharField(max_length = 200)
i18n_code = models.CharField(max_length = 200)
def __str__(self):
return self.name
#python_2_unicode_compatible
class Startup (models.Model):
name = models.CharField(max_length = 200)
# ...
areas = models.ManyToManyField(Area, verbose_name = 'Areas')
def __str__(self):
return self.name
form01.html
<div class="row linhaForm">
<div class="col-lg-4 legenda">
<b>{% trans 'FORM_1_LABEL_AREA' %}</b>
<p>{% trans 'FORM_1_LABEL_AREA_DESCRIPTION' %}</p>
</div>
<div class="col-lg-8">
{% for area in form.areas %}
<div class="checkbox">
{{area}} <-------------------- Here is my problem!
</div>
{% endfor %}
</div>
</div>
forms.py
from django.utils.translation import ugettext_lazy as _
class Step1Form(forms.ModelForm):
class Meta:
model = models.Startup
fields = ['name', 'areas']
widgets = {
'areas': forms.CheckboxSelectMultiple(attrs={'onclick':'onAreaCheckHandler()'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),
}
error_messages = {
'name': {
'required': _("FORM_1_ERROR_NAME_REQUIRED")
},
'areas': {
'required': _("FORM_1_ERROR_AREA_REQUIRED")
}
}
def __init__(self, *args, **kwargs):
super(Step1Form, self).__init__(*args, **kwargs)
if self.instance:
self.fields['areas'].queryset = models.Area.objects.all()
If you help me with this cenario, will help with the other that I have, where I got CharField with choices and ForeignKey fields too, rendered in a Select tag.
I believe this would fix your problem.
{% for area in form.fields.areas.queryset %}
<div class="checkbox">
{% trans area.i18n_code %}
</div>
{% endfor %}
I am answering my question to show you my solution after Blackeagle52 help!
(I was thinking... It was like programing PHP or ASP back in 2004)
For fields that have a queryset and widget CheckBoxSelectMultiple:
{% for area in form.fields.areas.queryset %}
<div class="checkbox">
<label for="id_areas_{{area.id}}">
<input id="id_areas_{{area.id}}" name="areas" type="checkbox" value="{{area.id}}" {% if area.id in form.areas.value %} checked="checked" {% endif %}>
{% trans area.i18n_code %}
</label>
</div>
{% endfor %}
For fields that have choices parameter (tuple of 2 values) and widget Select:
<select class="form-control" id="id_type_register" name="type_register">
{% for type in form.fields.type_register.choices %}
<option value="{{type.0}}" {% if type.0 == form.type_register.value %} selected="selected" {% endif %}>
{% trans type.1 %}
</option>
{% endfor %}
</select>
In both cases, if needed, just change queryset by choices and user your model properties (e.g. name, i18n_code, slug...) to be rendered. In case of tuples like [('1', 'One'), ('2', 'Two'), ('3', 'Three')] the model properties will be replaced by .0 or .1 after the name of the declared var in for statement.