Show possible choices of a model field in a template - django

In my exercise I have a Django model of a book, having a field "genre". This field has the following option choices
GENRES_CHOICE = (
('ADV','Adventure'),
('FAN','Fantasy'),
('POE','Poetry'),
)
and the model field is
genre = models.CharField(max_length = 3, blank = False, choices = GENRES_CHOICE, db_index = True, editable = False)
In my template I would like to show to the user the list of the genres (Adventure, Fantasy, Poetry) and hava available the keys, in order to possibly use them as parameters.
In order to do so, I would like to have a function that returns the data structure GENRES_CHOICE, but I am not able to. How to solve this problem?
EDIT:
more code details
appname= mybookshelf, file -> models/Book.py
# possible choices for the gerne field
GENRES_CHOICE = (
('ADV','Adventure'),
('FAN','Fantasy'),
('POE','Poetry'),
)
class Book(models.Model):
"""
This is the book model
...
## ATTRIBUTES (better use init, but in Django not always possible)
id = models.CharField(max_length = 64, blank = False, unique = True, primary_key = True, editable = False)
""" unique id for the element """
genre = models.CharField(max_length = 3, blank = False, choices = GENRES_CHOICE, db_index = True, editable = False)
""" book genre """
published_date = models.DateField(null = True, auto_now_add = True, editable = False)
""" date of publishing """
Then, into another file, lets say MyFunctions.py I have
from mybookshelf.models import GENRES_CHOICE
def getBookCategories():
"""
This function returns the possible book categories
categories = GENRES_CHOICE
return categories

views.py
from app_name.models import GENRES_CHOICE
def view_name(request):
...............
return render(request, 'page.html', {
'genres': GENRES_CHOICE
})
page.html
{% for genre in genres %}
{{genre.1}}<br/>
{% endfor %}

I am not 100% sure this is what you are after, but if you want to show the user the list of GENRES_CHOICE you can do this in your templete:
{% for choice_id, choice_label in genres %}
<p> {{ choice_id }} - {{ choice_label }} </p>
{% endfor %}
ofcourse pass GENRES_CHOICE as genres

You could use the get_modelfield_display() method in your template, e.g.:
{{ book.get_genre_display }}

Related

Django : Pass a prefetch_related _set.all() in Ajax Search results

I've implemented a table search product with Ajax and it works well.
But now, I want to build dynamically my table taking in account the number of my warehouses can be increase.
search.js
data.forEach((item) => {
const newName = (item.nom).slice(0, 30) + "...";
tableBody.innerHTML += `
<tr>
<th>${item.sku}</th>
<td>${item.etat__etat}</td>
<td class="small">${newName}</td>
<td>${item.famille__nom}</td>
<td>${item.mageid}</td>
<td>${item.adresse}</td>
models.py (model for witch I need a set)
class SstStock(models.Model):
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
product = models.ManyToManyField(Produit)
qty = models.IntegerField()
last_update = models.DateTimeField(default=timezone.now)
views.py
def search_product2(request):
if request.method == 'POST':
search_str = json.loads(request.body).get('searchText')
products = Produit.objects.filter(sku__icontains=search_str) | Produit.objects.filter(
nom__icontains=search_str) | Produit.objects.filter(mageid__icontains=search_str)
data = products.values(
'id',
'sku',
'nom',
[...]
'sststock',
[...]
'cau_cli',
'maxsst2',
)
return JsonResponse(list(data), safe=False)
Directly in template I could do :
template
{% for produit in produits %}
{{ produit.sku}}<br>
{% for sst in produit.sststock_set.all %}
<span>{{sst.warehouse.code}} - {{ sst.qty }}</span><br>
{% endfor %}
<br>
{% endfor %}
But I couldn't find the way to pass the the sststock_set.all() in the JsonResponse. I got well a "sststock" value in it but it contains only the last value of the set instead of an array/dict of the whole set.
console.log()
qty: 7
sku: "ACP863"
sststock: 68095
68095 is the last ID of my set.
Worse, when I try to get item.sststock in the ForEach product, in my JS, it returns Undefined.
Any idea please ?
Found the way to apply #WillemVanOnsem advice with serializer.
Before all, my first error war to apply ManyToMany instead of ForeignKey on:
product = models.ManyToManyField(Produit)
After, I have set a serializer that retrieves the different stocks (warehouse_id + qty) and adds it to the Product model (with "source" parameter):
serializers.py
from rest_framework import serializers
from .models import Produit, SstStock
class StockSearchSerializer(serializers.ModelSerializer):
class Meta:
model = SstStock
fields = '__all__'
fields = ['warehouse_id', 'qty']
class ProductSearchSerializer(serializers.ModelSerializer):
sststock = StockSearchSerializer(source='sststock_set', many=True)
class Meta:
model = Produit
fields = '__all__'
To finish, I use the serializer with "many=True" in the view and return its result that will be handled by JS on my search page:
views.py
def search_product(request):
if request.method == 'POST':
search_str = json.loads(request.body).get('searchText')
products = Produit.objects.prefetch_related(
Prefetch('sststock_set',
SstStock.objects.select_related('warehouse'))
).filter(sku__icontains=search_str) | Produit.objects.filter(
nom__icontains=search_str) |
Produit.objects.filter(mageid__icontains=search_str)
serializer = ProductSearchSerializer(products, many=True)
data = serializer.data
return JsonResponse(list(data), safe=False)
And as wished, stocks array is added in the json response

Django many-to-many making too many calls

I have a simple m2m relationship as below:
class Category(ModelBase):
name = models.CharField(max_length=255)
icon = models.CharField(max_length=50)
class Course(ModelBase):
name = models.CharField(max_length=255, unique=True)
categories = models.ManyToManyField(Category, related_name="courses")
I am using ListView to show all the courses in a category or all courses if no category provided.
views.py
class CourseListView(ListView):
model = Course
paginate_by = 15
template_name = "courses.html"
context_object_name = "courses"
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("tutor")
.prefetch_related("categories")
.filter(active=True)
)
category_id = self.kwargs.get("category_id")
return (
queryset
if not category_id
else queryset.filter(categories__in=[category_id])
)
def get_context_data(self, *args, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
category_id = self.kwargs.get("category_id")
if category_id:
context["current_category"] = Category.objects.get(id=category_id)
context["categories"] = Category.objects.all()
return context
Django is making duplicate calls as I am doing something like this in the template.
<div class="icon"><span class="{{ course.categories.first.icon }}"></span></div>
Not sure why, help much appreciated. Thanks!
When you do .prefetch_related('categories') the result of this prefetch will be used when you access course.categories.all. Any other queryset on course.categories will do a fresh query. Since course.categories.first is a new queryset, it does not use the prefetched result.
What you want to access in your template is the first result from course.categories.all(). But this is not easy in the template. I would recommend a method on the Course model:
class Course(...):
...
def first_category(self):
# Equivalent to self.categories.first(), but uses the prefetched categories
categories = self.categories.all()
if len(categories):
return categories[0]
else:
return None
And then in your template you can call this method
<div class="icon"><span class="{{ course.first_category.icon }}"></span></div>
You can also access the first value like:
{{ course.categories.all.0.icon }}
It is not necessary to write a method.
because categories is ManyToMany which means one category may appear in many courses, but in the template you just calling the first category's icon, so there maybe more than two course with the same first category, and it will retrieve them all, i recommend using another for loop to loops through categories too.
{% for course in courses %}
<div>
<h1>{{ course.name</h1>
......
<h4>categories</h4>
{% for category in course.categories %}
<div class="icon"><span class="{{ category.icon }}"></span></div>
{% endfor %}
</div>
{% endfor %}

Automatically show the foreignkey's other fields in django

I am trying to show the field(s) related to its foreignkey on html. Let's see I have two models as shown below:
models.py
from django.db import models
class Model_Item(models.Model):
item_name = models.CharField(max_length = 100, null = False, blank = False, unique = True)
item_unit = models.CharField(max_length = 20, null = False, blank = False) # can be kilogram, pound, ounce, etc
def __unicode__(self):
return self.item_name
class Model_Weight(models.Model):
item = models.ForeignKey(Model_Item, to_field = "item_name")
item_weight = models.FloatField(null = True, blank = True)
def __unicode__(self):
return self.item
On Model_Item model, each item can have its own unit, and there can be many items. Then we will choose the item on the second model (Model_Weight), and insert the value of the weight that is according to its unit.
How can we show the corresponding "item_unit" in html, such that when we have selected the "item_name", its unit will show/hover somewhere in the webpage which enables us to put the correct weight value?
These are the rest of the codes:
forms.py
from django import forms
from .models import Model_Weight
class Form_Weight(forms.ModelForm):
class Meta:
model = Model_Weight
fields = ["item", "item_weight"]
views.py
from .models import Model_Weight
from .forms import Form_Weight
from django.views.generic import CreateView
class View_Weight_CV(CreateView):
form_class = Form_Weight
def form_valid(self, form):
instance = form.save(commit = False)
instance.user = self.request.user
return super(View_Weight_CV, self).form_valid(form)
html
<form method = "POST" action = "" enctype = "multipart/form-data"> {% csrf_token %}
{{ form.item}}
<!-- {{ form.model_item.item_unit }} Automatically shows this field once an item has been selected -->
{{ form.item_weight}}
<input type = "submit" value = "Submit">
</form>
The quick solution is to change __unicode__ method definition of Model_Item model
def __unicode__(self):
# add item_unit with name
return self.item_name + " (" + self. item_unit + ")"
Now in your HTML template, the item dropdown will be shown like
{{ form.item }} #--> Bread (Kg)
#--> Rice (Kg)
#--> ...
If you want to show unit under item dropdown, keep above settings as it is and add below javascript code at bottom of your HTML template
$(document).on('change', '#id_item', function(){
// you can also make ajax request from here
// I am using selected item text for now
var item = $(this).find("option:selected").text();
// item = 'Bread (Kg)'
var result = item.match(/\((.*)\)/);
// matched text inside round brackets
// result[1] = Kg
$( "#id_item" ).after( "<p>"+result[1]+"</p>" );
});

Django formset fails date validation with valid date

I'm running into a date validation error with use of a django formset. I do not get the same validation error when I formset.is_valid(). The problem I'm experiencing is that the form is_valid check fails, only with the view and template use (not in the shell) specifically when using a date in the form of "March 20 2018" whereas it always passes with "2018-03-20".
Also I can verify the data is in the request.POST but the invalid due_date key is missing from self.cleaned_data when I look for it in the form's clean method. Perhaps that's normal given the invalid key but I would expect that to occur after the clean, not before, if at all. Feels like maybe its a django bug, I'm on django 2.0.2
Here's a summary of construction, its pretty vanilla:
# models.py
class Schedule(models.Model):
# ...
name = models.CharField(max_length=256)
status = models.CharField(max_length=16, default=choices.NOT_STARTED, choices=choices.SCHEDULE_STATUSES)
due_date = models.DateField(blank=True, null=True)
# ...
# forms.py
class ScheduleForm(forms.ModelForm):
class Meta:
model = models.Schedule
fields = ['name', 'user', 'status', 'due_date']
# views.py
def line_schedules_edit(request, line_slug):
line = get_object_or_404(models.Line, slug=line_slug)
queryset = line.schedules.all()
ScheduleFormSet = modelformset_factory(models.Schedule, form=forms.ScheduleForm)
if request.method == 'POST':
schedules_formset = ScheduleFormSet(request.POST)
if schedules_formset.is_valid():
schedules_formset.save()
return HttpResponseRedirect(reverse('products:line-schedules-edit',
kwargs={'line_slug': line_slug}))
else:
schedules_formset = ScheduleFormSet(queryset=queryset)
context = {
'line': line,
'formset': schedules_formset
}
return render(request, 'line-schedules-edit.html', context)
# template
{{ formset.management_form }}
{% csrf_token %}
{% for form in formset.forms %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
{{ field }}
{% endfor %}
{% endfor %}
With this structure I continually get an error of invalid date for due date when I use "March 3 2018" whereas if I provide a form input of "2018-03-18" in the browser, it works. Yet in a shell I'm able to verify that both date formats work:
In [35]: POST = {
'form-TOTAL_FORMS': '2',
'form-INITIAL_FORMS': '0',
'form-MAX_NUM_FORMS': '2',
'form-0-name': 'Test',
'form-0-status': 'Not started',
'form-0-due_date': '2018-03-20',
'form-1-name': 'Test',
'form-1-status': 'Not started',
'form-1-due_date': 'March 20, 2018'
}
In [36]: qdict = QueryDict('', mutable=True)
qdict.update(POST)
formset = ScheduleFormSet(qdict)
In [37]: formset.is_valid()
Out[37]: True
Why does the view and template fail the validation and why is the due_date key missing in the form's clean method?
Turns out all I needed to do was to provide input formats to pre-process the format before its sent off to the model. It must have been built-in model validation failing since it cannot store it in the form of "March 2 2018".
Using input_formats in the form, we can cast it to the desired format before the model processes it:
class ScheduleForm(forms.ModelForm):
class Meta:
model = models.Schedule
fields = ['name', 'user', 'status', 'due_date']
due_date = forms.DateField(widget=forms.DateInput(format='%d %B, %Y'),
input_formats=('%d %B, %Y',),
required=False)

Quering date and time field and render in template

views.py
def new_report(request):
user = request.user
reports = Report.objects.filter(user=user)
today = datetime.datetime.today()
reports_today = reports.filter(created_date_time__year=today.year, created_date_time__month=today.month, created_date_time__day=today.day)
num_today = len(reports_today) + 1
num_today = str(num_today).zfill(3)
reportform = ReportForm()
if request.method == 'POST':
reportform = ReportForm(request.POST)
if reportform.is_valid():
report = reportform.save(commit=False)
report.user = user
report.created_date_time = today
return render(request, 'incident/new_report.html',
{
'newreport_menu': True,
'reports': reports,
'reportform':ReportForm,
})
models.py
class Report(models.Model):
user = models.ForeignKey(User, null=False)
incident_number = models.CharField('Incident Number', max_length=100)
device_id = models.CharField('Device Id', max_length=100)
app_uuid = models.CharField('Unique App Id', max_length=100)
created_date_time = models.DateTimeField('Created',auto_now=True)
template is
{{ reports.created_date_time|date:"j M Y g:i A" }}
Fetching the created_date_time from database and convert to this format 20 Jan2011 at 2:26PM to display in template.
To query the created_date_time from database and display into template.I am not getting any error in the code,i think some logic having problem so that it is not happening.
reports variable is accessed in template as it is a single Report object, but it is a list of Report object.
To access to the first item in list in template, use following syntax:
{{ reports.0.created_date_time|date:"j M Y g:i A" }}
Now, the easiest way to print date in format you've mentioned is:
{{ reports.0.created_date_time|date:"j M Y" }} at {{ reports.0.created_date_time|date:"g:i A" }}
But, to my mind, the better way is to create custom template filter, that will print the desired format.
your_app/templatetags/format_dates.py
from django.utils.dateformat import format
from django.template.base import Library
register = Library()
#register.filter(expects_localtime=True, is_safe=False)
def format_date(value):
if not value:
return u''
try:
return u"%s at %s" % (format(value, 'j M Y'), format(value, 'g:i A'))
except AttributeError:
return u''
and then in template:
{% load format_dates %}
...
{{ reports.0.created_date_time|format_date }}