Where/how is Django ManyToManyField represented in the database? - python

UPDATE: Just found out that the ManyToManyField is causing the admin interface to crash, when a specific album is selected. I commented them out, commented out all references to it, reran makemigrations and migrate, and now the admin interface works again...which leaves me even farther away from making this "favorite" column work :( See this followup: Why is Django ManyToManyField causing admin interface to crash? Why is no through table being created?
Background: My goal is to make the "Favorite?" column in this webpage reflect the favorite albums of the currently-logged-in user, where each is either "no" or "yes", and is a clickable link to toggle the choice. (When not logged in, they will all be grey "n/a"-s.)
Therefore, for each album, there may be exactly zero or one "has favorited" entry per user. If the entry exists, they've favorited it. If it doesn't exist, they didn't.
Here is my Album model, with the favorited_by_users many-to-many column (full models.py at the bottom):
class Album(models.Model):
OFFICIALITY = (
('J', 'Major studio release'),
('I', 'Non-major official release'),
('U', 'Unofficial'),
)
title = models.CharField(max_length=70)
description = models.TextField(max_length=500, default="", null=True, blank=True)
pub_date = models.DateField('release date')
officiality = models.CharField(max_length=1, choices=OFFICIALITY)
is_concert = models.BooleanField(default=False)
main_info_url = models.URLField(blank=False)
thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
#virtual field to skip over the through table.
songs = models.ManyToManyField("Song", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def __str__(self):
return self.title
class Meta:
#Default ordering is by release date, ascending.
ordering = ['pub_date']
I originally had this FavoriteAlbum model, but since it has no extra information beyond the foreign keys, it was recommended that I eliminate it in favor of the above many-to-many column.
class FavoriteSongs(models.Model):
user = models.ForeignKey(User)
song = models.ForeignKey(Song)
class Meta:
unique_together = ('user', 'song',)
def __str__(self):
return "user=" + str(self.user) + ", song=" + str(self.song)
What I need to do is a "left join" between album and user, where all albums are selected, and any favorites by the currently-logged-in user are joined to it (None if they haven't favorited it). I don't know what to do.
I've also been told about the extra() function to do this join. The currently-working query in the view's get_queryset() is
return super(AlbumList, self).get_queryset().order_by("pub_date")
(Full views.py below.) My current guess is this:
return super(AlbumList, self).get_queryset().order_by("pub_date").extra(select={"is_favorite": "favorited_by_users__id = " + str(request.user.id) })
But, while this doesn't crash, the value of each {{ is_favorite }} in the template is nothing (the empty string). This makes sense since there's nothing in the database yet, but what now? I have no idea if this is the correct Django query.
I want to add an item in the database to test this, with a manual SQL statement in postgres (not via a Django command yet), but how and where do I do this?
I've successfully run makemigrations and then migrate with this new m2m column (and without the FavoriteSongs model), but I see nothing in the database that represents the is-favorite value. There's no extra column in billyjoel_album, and no through table akin to billyjoel_favoritealbum. So where/how is this data stored in the database?
(Any other advice regarding this extra "favorite" column would be appreciated as well!)
Thanks.
models.py
from django.db import models
from django.contrib.auth.models import User
from time import time
def get_upload_file_name(instance, filename):
return "uploaded_files/%s_%s" % (str(time()).replace(".", "_"), filename)
class Album(models.Model):
OFFICIALITY = (
('J', 'Major studio release'),
('I', 'Non-major official release'),
('U', 'Unofficial'),
)
title = models.CharField(max_length=70)
description = models.TextField(max_length=500, default="", null=True, blank=True)
pub_date = models.DateField('release date')
officiality = models.CharField(max_length=1, choices=OFFICIALITY)
is_concert = models.BooleanField(default=False)
main_info_url = models.URLField(blank=False)
thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
#virtual field to skip over the through table.
songs = models.ManyToManyField("Song", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def __str__(self):
return self.title
class Meta:
#Default ordering is by release date, ascending.
ordering = ['pub_date']
class Song(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=500, default="", null=True, blank=True)
length_seconds = models.IntegerField()
lyrics_url = models.URLField(default="", blank=True, null=True)
albums = models.ManyToManyField("Album", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def get_length_desc_from_seconds(self):
if(self.length_seconds == -1):
return "-1"
m, s = divmod(self.length_seconds, 60)
h, m = divmod(m, 60)
if(h):
return "%d:%02d:%02d" % (h, m, s)
else:
return "%d:%02d" % (m, s)
def __str__(self):
return self.name
class AlbumSong(models.Model):
song = models.ForeignKey(Song)
album = models.ForeignKey(Album)
sequence_num = models.IntegerField()
class Meta:
unique_together = ('album', 'sequence_num',)
unique_together = ('album', 'song',)
def __str__(self):
return str(self.album) + ": " + str(self.sequence_num) + ": " + str(self.song)
views.py
from .models import Album, Song, AlbumSong
from datetime import datetime, timedelta
from django.core.context_processors import csrf
from django.shortcuts import render, render_to_response
from django.views.generic import DetailView, ListView
from enum import Enum
def get_str_with_appended(string, between_if_str_non_empty, new_value):
if(len(string) == 0):
return new_value
else:
return string + between_if_str_non_empty + new_value
class PrependQuestionMark(Enum):
YES, NO = range(2)
def get_url_param_string_from_params(prepend_question_mark=PrependQuestionMark.YES, **kwargs_all_params):
param_list = ""
for key in iter(kwargs_all_params):
value = kwargs_all_params[key]
if(value is not None):
param_list = get_str_with_appended(param_list, '&', str(key) + "=" + str(value))
if(len(param_list) == 0):
return param_list;
if(prepend_question_mark == PrependQuestionMark.YES):
return "?" + param_list
else:
return param_list
class AlbumList(ListView):
model = Album
context_object_name = "albums"
#Derived from irc/#dango/tbaxter...START
def dispatch(self, request, *args, **kwargs):
#default to asc
self.sort_order = request.GET.get("sort_order", None)
self.sort_item = request.GET.get("sort_item", None)
self.csrf_token = csrf(request)["csrf_token"]
self.logged_in_user = request.user
#self.csrf_token = request.GET.get("csrf_token", None)
return super(AlbumList, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
#Item zero in both is the default
#should be static global
asc_desc_list = ["asc", "dsc"]
sort_by_types = ["pub_date", "title"]
if(self.sort_order is None and self.sort_item is None):
#Use default ordering
return super(AlbumList, self).get_queryset()
#Custom ordering requested
sort_order = self.sort_order
sort_item = self.sort_item
if(sort_order is None or
sort_order not in asc_desc_list):
sort_order = asc_desc_list[0]
if(sort_item is None or
sort_item not in sort_by_types):
sort_item = sort_by_types[0]
order_minus = "" if sort_order == "asc" else "-"
return super(AlbumList, self).get_queryset().order_by(order_minus + sort_item).extra(select={"is_favorite": "favorited_by_users__id = " + str(self.logged_in_user.id) })
def get_context_data(self, **kwargs):
context = super(AlbumList, self).get_context_data(**kwargs)
context["sort_order"] = self.sort_order
context["sort_item"] = self.sort_item
context["url_params"] = get_url_param_string_from_params(
sort_item=self.sort_item,
sort_order=self.sort_order,
csrf_token=self.csrf_token)
return context
class AlbumDetail(DetailView):
model = Album
context_object_name = "album"
def dispatch(self, request, *args, **kwargs):
#default to asc
self.sort_order = request.GET.get("sort_order", None)
self.sort_item = request.GET.get("sort_item", None)
self.csrf_token = csrf(request)["csrf_token"]
return super(AlbumDetail, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
#Call the base implementation first to get a context
context = super(AlbumDetail, self).get_context_data(**kwargs)
#Add in the required extra info: album_songs, ordered by
#sequence_num
#select_related is to query the database for all songs at once, here
#in the view, to prevent the template from pin-pricking the database
#in each for loop iteration. For large datasets, this is critical.
context['album_songs'] = kwargs["object"].albumsong_set.order_by('sequence_num').select_related("song")
context["url_params"] = get_url_param_string_from_params(
sort_item=self.sort_item,
sort_order=self.sort_order,
csrf_token=self.csrf_token)
return context
album_list.html
{% extends "base.html" %}
{% load bj_filters %}
{% block title %}Billy Joel Album Browser{% endblock %}
{% block sidebar %}
<UL>
<LI>All albums</LI>
<LI>Admin</LI>
</UL>
{% endblock %}
{% block content %}
<TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
{% if user.is_authenticated %}
<TD>My profile (Logout)</TD>
{% else %}
<TD>Login to view your favorites</TD>
{% endif %}
</TR></TABLE>
<H1>Billy Joel Album Browser</H1>
<!--
<P>url_params={{ url_params }}</P>
-->
{% if albums.count > 0 %}
<P>Officiality: <IMG SRC="/static/images/major.jpg" height="20"/>=Major studio release, <IMG SRC="/static/images/minor.jpg" height="20"/>=Official release, <IMG SRC="/static/images/unofficial.jpg" height="20"/>=Unofficial</P>
<TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
<TD><B><U><a href="{% url 'album_list' %}?sort_item=title&sort_order=
{% if sort_item == 'pub_date' %}asc{% else %}
{{ sort_order|multival_to_str:'asc,dsc->dsc,asc,dsc' }}
{% endif %}
&csrf_token={{ csrf_token }}">Title</A></U></B><BR><I><FONT SIZE="-1">(click a title to view its song list)</FONT></I></TD>
<TD><B><U><a href="{% url 'album_list' %}?sort_item=pub_date&sort_order=
{% if sort_item == 'title' %}asc{% else %}
{{ sort_order|multival_to_str:'asc,dsc->dsc,asc,dsc' }}
{% endif %}
&csrf_token={{ csrf_token }}">Released</A></U></B></TD>
<TD>Officiality</TD>
<TD>Concert</TD>
<TD>Wiki</TD>
<TD>Favorite?</TD>
{% for album in albums %} <!-- No colon after "albums" -->
</TR><TR>
<TD VALIGN="top">
{% if album.thumbnail %}
<img src="/static/{{ album.thumbnail }}" width="25"/>
{% else %}
<img src="/static/images/white_block.jpg" width="25"/>
{% endif %}
{{ album.title }}
{% if album.description %}
<BR/><FONT SIZE="-1"><I>{{ album.description|truncatewords:10 }}</I></FONT>
{% endif %}
<TD>{{ album.pub_date|date:"m/y" }}</TD>
<TD><IMG SRC="/static/images/{{ album.officiality|multival_to_str:"J,I,U->major,minor,unofficial,broken_image"}}.jpg" height="20"/></TD>
<TD>{{ album.is_concert|yesno:"Yes,No" }}</TD>
<TD>Wiki</TD>
<TD><I>n/a {{ is_favorite }}</I></TD>
{% endfor %}
</TR></TABLE>
{% else %}
<P><I>There are no albums in the database.</I></P>
{% endif %}
{% endblock %}

M2M relationships are brand-new tables created. They get an unique name, and have two foreign keys. A composite key is created so the direct and the related model combinations are unique.
When you do:
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas")
#not including a related_name will generate a "pizza_set" related name.
A new table appears describing the relationship, with an internal name. This table has a pizza_id and a topping_id foreign key, and a composite unique key including both fields. You cannot, and should not, predict the name of such table.
On the other side, if you want to access the relationship and, possible, declare more fields, you can:
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas", through="PizzaAndTopping")
#not including a related_name will generate a "pizza_set" related name.
class PizzaAndTopping(models.Model):
more_data = models.TextField()
pizza = models.ForeignKey(Pizza, null=False)
topping = models.ForeignKey(Topping, null=False)
class Meta:
unique_together = (('pizza','topping'),)
Notice how I added a through parameter. Now you have control of the middle-table BUT you cannot add or delete models from the relationship. This means, with this approach you cannot:
Pizza.objects.get(pk=1).toppings.append(Topping.objects.get(pk=2))
Nor you can remove, nor you can do these operations in the Topping side of the life.
If you want to add or delete toppin-pizza relationships, you must do it directly in the PizzaAndTopping relationship.
If you want to know whether the current user has marked any song as their favorite, you should prefetch the relationship. In Django 1.7, you can prefetch a M2M related field using a custom filter: you can fetch all the albums, and only a query getting the current user, using a Prefetch object. See the official docs about prefetching here.
Antoher solution would involve:
fetching the current user's favorite albums list: ulist = user.album_set.all()
fetching the current page of alboms: _list = Album.objects.all()[0:20]
fetch values of user albums: ulist = ulist.values_list('id', flat=True)
[1, 2, 4, 5, 10, ...] #you'll get a list of ids
when iterating over each album in the page, you test currentAlbum.id in ulist and print a different message (either yes or no).

The many-to-many field is represented in the database in exactly the same way as your original FavouriteSongs model - as a linking table with ForeignKeys to both Song and User. The only benefit of getting rid of FavouriteSongs is that you're now using an automatically-defined through table, rather than a manual one.
I don't understand your example query, since you don't say what model you are actually calling it on, or what self.logged_in_user is. However, you can't use extra like this: you are trying to put Django query syntax there, complete with double-underscore names to traverse relationships, but extra is passed directly to the SQL, and that doesn't know anything about that syntax.
I would not attempt to do this in one query. Instead I would do two queries, one to get all the albums and one to get the user's favourites. get_queryset would just return the full album list, and then you can use get_context_data to get an additional set of objects representing the IDs of the favourites:
favorites = self.logged_in_user.album_set.all().values_list('id', flat=True)
context['favorites'] = set(favorites)
The values_list just gets the IDs of the albums only, since that's all we need, and we then put them into a set to make lookups quicker.
Now, in the template, you can just do:
{% for album in albums %}
...
<td>{% if album.id in favorites %}Yes{% else %}No{% endif %}</td>
{% endfor %}

Related

Rendering list of forms in Django

I am trying to create a basic personality test in Django as a proof-of-concept at work. I'm new to Django (and python in general), coming at it from a C# .NET background.
I am trying to make a list of form objects (populated with information pulled from question objects stored in the database), then display them in the HTML.
This is only partly working; I can render the form attributes individually in a for loop (by calling, for example, question.pk) but nothing renders with the standard Django {{ form }} tag, and trying to submit the list of forms breaks the whole thing.
I'm pretty sure it's an issue with handling a bunch of form objects populated inside one larger html , but I'm not sure how to go about resolving it.
I've done some research into formsets, but I can't find any way to pre-populate the form items with information from the database.
Thanks in advance!
DISCQuestionForm in forms.py:
class DISCQuestionForm(forms.Form):
# create new form object from database question object
def __init__(
self,
pk,
disc_query,
dom_answer,
infl_answer,
stead_answer,
con_answer,
):
super().__init__()
self.pk = pk
self.disc_query = disc_query
self.dom_answer = dom_answer
self.infl_answer = infl_answer
self.stead_answer = stead_answer
self.con_answer = con_answer
self.disc_response = forms.DecimalField(
max_value=4,
widget=forms.NumberInput
)
disc_create method in views.py
# Create a new DISC assessment for current user
def disc_create(request, pk):
profile = User.objects.get(pk=pk)
user = int(profile.pk)
name = profile.name
rawquestionset = DISCQuestion.objects.all()
discformset = []
for item in rawquestionset:
question = DISCQuestionForm(
pk=item.pk,
disc_query=item.disc_query,
dom_answer=item.dom_answer,
infl_answer=item.infl_answer,
stead_answer=item.stead_answer,
con_answer=item.con_answer,
)
discformset.append(question)
if request.method == 'POST':
questionset = discformset[request.POST]
if questionset.is_valid():
dom = 0
infl = 0
stead = 0
con = 0
for discquestion in questionset:
if discquestion.disc_response == discquestion.dom_answer:
dom += 1
if discquestion.disc_response == discquestion.infl_answer:
infl += 1
if discquestion.disc_response == discquestion.stead_answer:
stead += 1
if discquestion.disc_response == discquestion.con_answer:
con += 1
disctest = DISCTest(
user=user,
name=name,
dom=dom,
infl=infl,
stead=stead,
con=con,
)
disctest.save()
else:
questionset = discformset
context = {
"pk": user,
"name": name,
"discquestionset": questionset
}
return render(request, "disc_create.html", context)
DISCTest and DISCQuestion models in models.py:
class DISCTest(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
name = user.name
created_on = models.DateTimeField(auto_now_add=True)
dom = models.DecimalField(max_digits=3, decimal_places=0)
infl = models.DecimalField(max_digits=3, decimal_places=0)
stead = models.DecimalField(max_digits=3, decimal_places=0)
con = models.DecimalField(max_digits=3, decimal_places=0)
class DISCQuestion(models.Model):
disc_query = models.TextField()
disc_response = models.DecimalField(max_digits=1, decimal_places=0, null=True)
dom_answer = models.DecimalField(max_digits=1, decimal_places=0)
infl_answer = models.DecimalField(max_digits=1, decimal_places=0)
stead_answer = models.DecimalField(max_digits=1, decimal_places=0)
con_answer = models.DecimalField(max_digits=1, decimal_places=0)
and finally disc_create.html in templates:
{% extends "base.html" %}
{% block page_content %}
<div class="col-md-8 offset-md-2">
<h1>Take your DISC assessment</h1>
<hr>
<h3>Insert instructions here</h3>
<hr>
<form action="/assessment/create/{{pk}}/" method="post">
{% csrf_token %}
<div>
{% for question in discquestionset %}
<p>{{question.pk}}</p>
<p>{{ question.disc_query }}</p>
{{ form }}
{% endfor %}
</div>
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
Your DiscQuestionForm has no fields. disc_response is defined as an attribute of the form but for Django it isn't a field because it isn't added to self.fields. And form isn't defined in your template in your for loop, only question (which is the form) so {{ question }} would print the form if it had any fields.
But then the problem is that each of your question form fields would all have the same "name" attributes because they are not prefixed to make them unique.
You should read this document carefully to understand ModelForm and modelformset. Basically you need:
class DISCQuestionForm(forms.ModelForm):
class Meta:
model = DISCQuestion
def __init__(...):
...
Use the modelformset_factory to create a proper ModelFormSet that you can initialise with the request.POST when submitted.
DISCQuestionFormSet = modelformset_factory(DISCQuestionForm, form = DISCQuestionForm) # note DISCQuestionForm not needed if you don't customise anything in your form.
and in your view:
formset = DISCQuestFormSet(request.POST or None)
then in your template you can loop through the forms in the formset:
{% for form in formset %}{{ form }}{% endfor %}

Use Key-Value Lookup for Second Model in Django Template For Loop?

I'd like to figure out a way to use a key-value lookup for a second model within a Django for loop.
This question on dictionary key-value loops in templates is on the right track, but I am using two normalized data models. There is a 'Parent' data object (EventDetail) that contains the relevant link and 'Child' data object (DateEvent) that has date-specific information with data on user actions.
I have a template tag, link_if_exists, that is being duplicated many times. Django-debug-toolbar tells me this is being duplicated 76 times right now. This 'duplicate' message is, itself, duplicated many times.
This is what I have now:
app_extras.py
#register.filter()
def link_if_exists(title):
"""Return a link if it exists"""
event_link = None
try:
event_link = EventDetail.objects.filter(title=title).values_list('link', flat=True)[0]
except IndexError:
pass
if event_link != "":
return event_link
return False
template.html
{% load 'app_extras' %}
{% for item in child_date_items %}
{% if item.title|link_if_exists %}
{{item.title}}
{% endif %}
{% endfor %}
models.py
class DateEvent(models.Model)
title = models.CharField(max_length=250)
event_date = models.DateField(default=date.today)
event_datetime = models.DateTimeField(auto_now=False)
class EventDetail(models.Model)
title = models.CharField(max_length=250)
link = models.URLField(max_length=200, default="", blank=True)
views.py
class ProblemView(TemplateView):
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(ProblemView, self).get_context_data(**kwargs)
date_today = utils.get_date_today()
child_date_items = DateEvent.objects.filter(event_date=date_today)
context['child_date_items'] = child_date_items
return context
Django-debug-toolbar output
SELECT link FROM
table WHERE title='title'
...
| duplicated 74 times
Something like this is what I am after:
Add 'event_detail' to views.py
class NewView(TemplateView):
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(NewView, self).get_context_data(**kwargs)
date_today = utils.get_date_today()
child_date_items = DateEvent.objects.filter(event_date=date_today)
context['child_date_items'] = child_date_items
event_detail = EventDetail.objects.all()
context['event_detail'] = event_detail
return context
Lookup title as key to get 'link' value in ideal_template.html
{% for item in child_date_items %}
{% if event_detail[item.title]['link'] %}
{{item.title}}
{% endfor %}
{% endfor %}
I don't know if this functionality exists so I am open to other suggestions. I am also open to computing this in views.py and iterating over a common object in the template. I understand that I could duplicate the link data and just add a link column in the DateEvent model, but that seems wasteful and I'd like to avoid that if possible. This isn't the only field I need this type of logic, so adding everything to the Child object would take up a lot of extra space in the database.
You need to do some work on your models. If there is a one-to-many relationship between them (several DateEvents for one EventDetail), then rather than duplicating title manually in both of them and then linking both again manually, you should formalize the relationship at the model level:
class EventDetail(models.Model)
title = models.CharField(max_length=250)
link = models.URLField(max_length=200, default="", blank=True)
class DateEvent(models.Model)
event_detail = models.ForeignKey(EventDetail, on_delete=models.CASCADE)
event_date = models.DateField(default=date.today)
event_datetime = models.DateTimeField(auto_now=False)
Then you can refer to the link field from any DateEvent object without any conditionals and duplicate queries (if you use select_related() properly).
So if you're working with DateEvents, you'd use:
date_events = (
DateEvent.objects
.filter(event_date=date_today)
.select_related('event_detail')
)
Then, in the template:
{% for item in child_date_items %}
{{item.event_detail.title}}
{% endfor %}

Django foreign key select field empty

I've been working on a small django website as I learn django. According to the documentation when you create a form class with a meta class that points at a model with foreign key fields, it'll render those fields as select inputs.
In my application I have 3 models, client test, and record where record carries two foreign keys, each of whom point to client and test respectively
Models.py
class Client(models.Model):
first = models.CharField(max_length=264)
last = models.CharField(max_length=264)
DOB = models.DateField()
def __str__(self):
return self.first + " " + self.last
class Test(models.Model):
test = models.CharField(max_length=264)
fee = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self):
return self.test
class Record(models.Model):
client = models.ForeignKey(Client, on_delete=models.CASCADE)
test = models.ForeignKey(Test, on_delete=models.CASCADE)
date = models.DateField()
def __str__(self):
return str(self.date) + " " + str(self.test) + " for " + str(self.client)
Form.py
class NewLabRecord(forms.ModelForm):
client = forms.ChoiceField(
label='Client ID',
widget=forms.Select(
attrs={'class': 'form-control'}))
test = forms.ChoiceField(
label='Test ID',
widget=forms.Select(
attrs={'class': 'form-control'}))
date = forms.DateField(
label='Test Date',
widget=forms.DateInput(
attrs={'class': 'form-control'}))
class Meta:
model = models.Record
fields = '__all__'
I render NewLabRecord at the top of my index view for records. The idea is to create a record and redirect back to the page (therefore seeing it in the list of records). Presently, I'm emulating class-based-views and not actually implementing it because I have not learned it yet. Nevertheless, this pattern does work for my client and test (the code is nearly identical).
Views.py
class LabRecord:
#staticmethod
def index(request):
new_record_form = forms.NewLabRecord
records = models.Record.objects.order_by('date')
print(records)
context = {
'records': records,
'new_record_form': new_record_form
}
return render(request, "layouts/lab/record/index.html", context=context)
layouts/lab/record/index.html
<div class="collapse" id="createLabRecord">
{% include 'components/lab/record/create.html' %}
</div>
components/lab/record/create.html
<!DOCTYPE html>
{% block content %}
<div class="card col-sm" style="">
<form class="form" method="post" action="{% url 'lab:create lab record' %}">
{{ new_record_form }}
{% csrf_token %}
<input type="submit" value="submit">
</form>
</div>
{% endblock %}
Now, when I go to the url for this view, /lab/records/, the view renders two select fields and an input for the date; however, the select fields are empty!
Note: I have 9 clients and 4 tests in the database!
Why is the view generating empty select fields for the foreign key fields?
In your view, you need to query your Client and Test models and put those lists into your context to make them available to your form/template.
records = models.Record.objects.order_by('date')
clients = models.Client.objects.all()
tests = models.Test.objects.all()
context = {
'records': records,
'new_record_form': new_record_form,
'clients' : clients,
'tests' : tests,
}
I have not learned the forms portion of django yet to tell you if there is something else to connect the lists to the input select fields.
Edit:
It looks like the following in your form should accomplish the desired:
class NewLabRecord(forms.ModelForm):
client = forms.ModelChoiceField(models.Client.objects.all())
test = forms.ModelChoiceField(models.Test.objects.all())
date = forms.DateField(
label='Test Date',
widget=forms.DateInput(
attrs={'class': 'form-control'}))
class Meta:
model = models.Record
fields = '__all__'
And I don't think that the changes to your view are then necessary.

Django Get Related with Multiple Models

Hi I'm new in Django and don't know how to get related objects with multiple models.
My code:
#models.py
class Candidate(models.Model):
user = models.OneToOneField(User, primary_key=True)
birth = models.CharField(max_length=50)
...
class CandidatePhotos(models.Model):
user = models.ForeignKey(User)
photo = models.ImageField(upload_to='usergallery/%Y/%m/%d')
class Job(models.Model):
candidate = models.ManyToManyField('Candidate', through='CandidateToJob')
title = models.CharField(max_length=500)
...
class CandidateToJob(models.Model):
job = models.ForeignKey(Job, related_name='applied_to')
candidate = models.ForeignKey(Candidate, related_name='from_user')
STATUS_CHOICES = (
('1', 'Not approved'),
('2', 'Approved'),
('3', 'Hired')
)
status = models.CharField(max_length=2, choices=STATUS_CHOICES)
In the views I have
#views.py
class CandidateDetails(generic.DetailView):
model = Candidate
template_name = 'dashboard/candidate.html'
def get_context_data(self, **kwargs):
context = super(CandidateDetails, self).get_context_data(**kwargs)
context['cand_photos'] = CandidatePhotos.objects.all()
return context
In the template I have
<h2>{{ candidate.user.first_name }} {{ candidate.user.last_name }}</h2>
{% for candidatephotos in cand_photos %}
<img alt="" src="{{ candidatephotos.photo.url }}" >
{% endfor %}
Here is my url dashboard/candidate/pk/.
What happens is in the template all users photos are loaded instead of only the specific user I want.
I have tried to get the user photos using {{ candidate.user.candidatephotos_set.photo.url }} but it doesn't work.
I have also tried to change in views.py the model from "Candidate" to "CandidateToJob" (the through model) but I get a 404 error, I don't know why.
So, what is the best practice to achieve this?
There's no need to query CandidatePhotos in the view at all. Your candidate object already had the relevant relationship, through User, so you can simply follow that:
{% for photo in object.user.candidatephotos_set.all %}

Caught AttributeError while rendering: 'unicode' object has no attribute '_default_manager'

Get this error following Practical Django tutorial. Have searched here and Google and haven't found any mentions of this, so I imagine it's going to be something easy I am missing. Exception is in gambino_tags.py:
context[self.varname] = self.model._default_manager.all()[:self.num]
models.py
class Entry(models.Model):
# Entry types
LIVE_STATUS = 1
DRAFT_STATUS = 2
HIDDEN_STATUS = 3
STATUS_CHOICES = (
(LIVE_STATUS, 'Live'),
(DRAFT_STATUS, 'Draft'),
(HIDDEN_STATUS, 'Hidden'),
)
# Foreign Key
author = models.ForeignKey(User)
title = models.CharField(max_length=250, help_text='Maximum 250 characters.')
# Bit of redundancy with excerpt & excerpt_html and body & body_html, but seems to be cleanest way to seperate plain text and html
excerpt = models.TextField(blank=True, help_text='Add an excerpt - a short summary of your post.')
excerpt_html = models.TextField(editable=False, blank=True)
body = models.TextField(help_text='Add the content of your post.')
body_html = models.TextField(editable=False, blank=True)
pub_date = models.DateTimeField(default=datetime.datetime.now)
# Slug prepopulated from title in admin.py
slug = models.SlugField(unique_for_date='pub_date', help_text="Suggested value automatically generated from title. Must be unique.")
enable_comments = models.BooleanField(default=True)
featured = models.BooleanField(default=False, help_text="Make this post featured. Will provide additional sorting.")
status = models.IntegerField(choices=STATUS_CHOICES, default=LIVE_STATUS)
categories = models.ManyToManyField(Category)
tags = TagField()
# Adds objects and live override from LiveEntryManager class above
live = LiveEntryManager()
objects = models.Manager()
gambino_tags.py
from django import template
from django.db.models import get_model
def do_latest_content(parser, token):
bits = token.contents.split()
# Checks to make sure that there are 5 words in the tag
if len(bits) != 5:
raise template.TemplateSyntaxError("'get_latest_content' tag takes exactly four arguments")
model_args = bits[1].split('.')
# Checks to make sure first argument is appname.model name
if len(model_args) != 2:
raise template.TemplateSyntaxError("First argument to 'get_latest_content' must be an 'application name'.'model name' string")
model = get_model(*model_args)
# Checks to make sure model is != none
if model is None:
raise template.TemplateSyntaxError("'get_latest_content' tag got an invalid model: %s" % bits[1])
return LatestContentNode(bits[1], bits[2], bits[4])
class LatestContentNode(template.Node):
def __init__(self, model, num, varname):
self.model = model
# convert num to int
self.num = int(num)
self.varname = varname
def render(self, context):
# uses default manager for Entry.live.all() instances
context[self.varname] = self.model._default_manager.all()[:self.num]
return ''
register = template.Library()
register.tag('get_latest_content', do_latest_content)
base.html
{% load gambino_tags %}
<h2>Five latest entries:</h2>
<ul>
{% get_latest_content gambino.entry 5 as latest_entries %}
{% for entry in latest_entries %}
<li>
<a class="list" href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
Posted {{ entry.pub_date|timesince }} ago.
</li>
{% endfor %}
</ul>
<h2>Five latest links:</h2>
<ul>
{% get_latest_content gambino.link 5 as latest_links %}
{% for link in latest_links %}
<li>
{{ link.title }}
Posted {{ link.pub_date|timesince }} ago.
</li>
{% endfor %}
</ul>
Thanks!
The model argument of your Node's __init__ is a string (or at least, that's what you're passing in), not a Model object.
bits = token.contents.split()
...
return LatestContentNode(bits[1], bits[2], bits[4])
...
class LatestContentNode(template.Node):
def __init__(self, model, num, varname):
self.model = model
...
context[self.varname] = self.model._default_manager.all()[:self.num]
Perhaps you actually meant...
return LatestContentNode(model, bits[2], bits[4])
?

Categories