Basic model design: Book and Categories - django

I haven't taken any database course. Please excuse me...
From Django Book tutorial, we have a book application which has three classes initially (Book, Publisher and Author).
class Book(models.Model):
title = models.CharField(max_length=100)
# categories = models.ManyToManyField(Category)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication = models.DateField()
We can match a book to many authors while we can match only a book to a Publisher.
A book can have several categories so it seems natural to write what I've commented out above.
class Category(models.Model):
name = models.CharField(max_length=40)
Second attempt:
I remember reading this somewhere (vaugly): create BookCategories which takes Category as ForeignKey.
Book --> --> BookCategories --> Categroy
(Notation: double arrows = ManyToMany and single arrow = ForeginKey)
Which one is better? If none, how do I approach this? Where is my logic flaw? If the second attempt is better, what is the reason?
Thank you very much.

I believe the approaches you have described are more or less identical. A many-to-many field always requires an interstitial or 'through' table to store the links between the two object types.
In the first approach you've described, Django will create the through table automatically when you run syncdb. In the second approach you've just defined that table manually (although as you've specified it you would have a redundant table). The second approach is useful in some circumstances, because it means you can not only define a link, but also store information about that link. In this case, you might have a field that stores the time when a book was first added to a category, or the user that added it.
For more information, check out the Django docs on many-to-many through tables.

Related

Django - Many to Many Relationship with Workouts & Exercises

I'm currently stuck on how to do the following:
I want to track workouts and see the progress being made with the exercises.
Each workout has a name, body weight, date, and exercises.
I want to select the exercises when adding the workouts(exercises are already pre-created with a name and some tags), however, each workout is different, meaning the reps, sets, and weight for the exercise is different every time.
I'm not sure how to make a model for this.
I've added an image of a simple design I made to show what I'm after
Hope someone can help me, I'm pretty sure it's a simple solution however, my brain is stuck atm.
Image Of Design
First an foremost, I'd recommend reading through the Django models docs to get an idea of what models represent and how they work.
To address your question, I think you've correctly identified all of the information you need to create your models, so let's go through them step by step.
Architecture
It's helpful to take a step back and think about the entities in your app. Identify what they are, what attributes they posses and which of these attributes are atomic i.e. cannot be their own entity.
Workout - You mentioned that you want to track workouts with each one having name, body weight, date, and exercises. All of these attributes except for exercises seem to be atomic as they can be represented with fundamental datatypes (strings, floats, datetimes etc.). Moreover, one workout can have many exercises indicating that we need to abstract it into its own entity.
Exercise - You identified that exercises are pre-set and need to have a name and tags. A name is something we can represent with a string, however one exercise can have multiple tags, meaning it's not atomic (has a one-to-many relationship). This means we need to extract it into its own entity.
Tag - From what you said, a tag simply has one attribute which is a name that can be represented by a string. One tag can belong to many exercises.
You may be wondering where we are storing the reps, sets and weight data for each exercise in each workout. This is actually going to require an extra entity that stores the many-to-many relationship between Exercise and Workout. Let's call this Workout-Exercise.
With this information we could draw a Entity Relationship Diagram as such:
This gives us what we need to start creating Django models.
Models
Let's start with the Exercise and Tag entities. We can simply translate these directly into Django models:
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=200)
class Exercise(models.Model):
name = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag)
Here we've created the two models and specified a many-to-many relationship between Exercise and Tag. This means that an Exercise object can have many Tag objects e.g. you can call exercise.tags.all() to get all of the tags for a given Exercise object.
The tricky part comes when we are creating the Workout-Exercise entity. When we use ManyToManyField in Django, it normally automatically creates a mapping model/table that we don't see. However, in the case where we want to store extra information about these relations (as we do in our use-case) we have to use a through model.
On this model we have to define the two foreign keys for the models we are linking along with the data types for the extra field data we want to store. In this case the foreign keys are Workout and Exercise, and the extra data are reps, sets and weight. The model definitions could therefore look like:
class WorkoutExercise(models.Model):
workout = models.ForeignKey(
'Workout',
on_delete=models.CASCADE,
)
exercise = models.ForeignKey(
Exercise,
on_delete=models.CASCADE,
)
reps = models.IntegerField()
sets = models.IntegerField()
weight = models.DecimalField(max_digits=5, decimal_places=2)
class Workout(models.Model):
name = models.CharField(max_length=200)
body_weight = models.DecimalField(max_digits=5, decimal_places=2)
date = models.DateTimeField(auto_now_add=True)
exercises = models.ManyToManyField(
Exercise,
through=WorkoutExercise
)
If you're confused about any of the model data type choices I've recommended, please take a look at Django model docs.
With this set-up you should be able to access and insert all of the data you need. If you need more information on how to access any of the many-to-many relationship data, please look at the Django many-to-many docs.
Sources
https://docs.djangoproject.com/en/3.2/topics/db/models/
https://www.1keydata.com/database-normalization/first-normal-form-1nf.php
https://en.wikipedia.org/wiki/One-to-many_(data_model)
https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many

On two related models, which one should contain the definition of the relationship?

First of all, yes: I've read Django's foreign key and many-to-many documentation, but I'm still not 100% clear on how to implement relationships on a practical level, especially regarding the hierarchy of the relationships.
One-to-one
I am aware of how to form one-to-one relationships. However, on a more conceptual level, which model should contain that reference to the other one? Let's say I have a Citizen, and a Passport. Now, it's obvious that one Citizen can only have a single Passport and viceversa, but, ideally, should the Citizen contain a field referencing to his Passport, or should the Passport model contain a reference to the Citizen it belongs to?
Many-to-many
For the sake of simplicity, let's say I have a Person model and a Trip model (Trip as in going out on a trip somewhere). Many Persons can participate in a single Trip. Or in other words: a Person can participate in many Trips and in any single Trip, a lot of Persons can participate. This looks like a many-to-many relationship, but, again, ideally, which model should contain the definition for the relationship, the Person with a trips field or the Trip with a participants field? And why? Does it even make any practical difference?
Thank you.
This depends on your business logic. As a rule of thumb I'd suggest to think about the admin app. How would you like to add new objects?
When adding new objects, how would you like to add related objects?
Let's say you have these models:
Citizen(models.Model):
name = models.CharField()
Passport(models.Model):
number = models.CharField()
citizen = models.OneToOneField('Citizen', related_name='passport')
When adding new passport object, you have the possibility to add new citizen, if it doesn't yet exist. Since this doesn't look very logical to me, I'd change the relation as:
Citizen(models.Model):
# other fields
passport = models.OneToOneField('Passport', related_name='citizen')
Now we can add a new citizen object in the admin and add the related passport object within the same page.
If you use the admin app, this should lead you to more ergonomical design.
EDIT: expand with many-to-many example
Better example for a m2m relation would be StackOverflow - there are questions and tags. A question has many tags, and a tag has many questions. Let's say the models look like this:
Question(models.Model):
title = models.CharField()
body = models.TextField()
author = models.CharField()
tags = models.ManyToManyField('Tag', related_name='questions')
Tag(models.Model):
name = models.CharField()
Why do we put the relation in Question? This should be very logical - when creating a new question you'd like to set the tags for it. When creating a new tag you don't care about any questions associated with it. You can create a tag and later when creating questions, associate them with the tag.
If a tag doesn't exist yet you can add it from the admin, when adding a new question.
I hope this second example is more palpable.
The theory behind this is called database normalization which is a ladder of best practices you should look up if you want to know more about how to structure your data.
The third form tells us that:
"[Every] non-key [attribute] must provide a fact about the key, the whole key, and nothing but the key."
So in the case of ForeignKey fields it should be on the Child model, because it doesn't tell us anything about the parent, but it does tells us what parent the child belongs to.
The mental model that you should have is Parent and Child. Every relationship has two models. So think of one as the Parent model or the Primary model and think of the other one as the Child model or the Secondary model.
NOTE: Always put your relationship field in the CHILD model.
Here is how I would solve your problems:
For the first one, I will have a mental model that Citizen is the Parent and Passport is the child.
class Citizen(models.Model):
name = models.CharField(max_length=255)
info = models.TextField()
class Passport(models.Model):
owner = models.OneToOneField(Citizen)
unique_no = models.CharField(max_length=30, unique=True)
For the second problem, do the same. I would choose Person as the parent model and Trip as the child model.
class Person(models.Model):
name = models.CharField(max_length=255)
info = models.TextField()
class Trip(models.Model):
person = models.ManyToManyField(Person)
info = models.TextField()
If you have sqlitebrowser, you can use that to open your database and check what tables were created according to your models. Then, you will have a clearer idea as to how Django sees your models.

Django - filter manytomany field - request.LANGUAGE_CODE

I've been experiencing a problem with filter and m2m relation and request.LANGUAGE_CODE.
If in a view I use request.LANGUAGE_CODE in first example the query returns all possible entries for each available language.
While in second model everything works perfectly.
Example - not working:
Model:
class Publisher(models.Model):
publication = models.ManyToManyField('Translation', related_name="")
View:
pub = Publisher.objects.filter(publication__language=request.LANGUAGE_CODE)
Example - working:
Model:
class Publisher(models.Model):
title = models.CharField(max_length=256, verbose_name="Tytuł", blank=True, null=True)
text = models.TextField(verbose_name="Treść", blank=True, null=True)
language = models.CharField(max_length=8, choices=settings.LANGUAGES, default=settings.LANGUAGES[1])
View:
pub = Publisher.objects.filter(language=request.LANGUAGE_CODE)
How does that work and how to fix or avoid that issue?
The language attribute is part of Translation objects in the first example, and part of Publisher in the second one. Also, in the first one Publisher has no text of its own, but references M2M to Translation objects which hold the text.
So in the first example you are asking it to select Publishers, not Translations (which are linked by the member publication). So if any Publisher has any Translation object in the queried language, you will get that Publisher, and then when you do something like pub.publication.all() it will do what you are asking: It will return EVERY publication object associated with that Publisher of any language.
To fix the first example you need to do something like
pub = Translation.objects.filter(language=request.LANGUAGE_CODE)
if you just want a list of all articles in a certain language, because the Translation objects have the text and the language setting data, not the Publisher.
If you want a list of all articles in a specific language from one specific publisher, add another filter variable, like:
pub = Translation.objects.filter(language=request.LANGUAGE_CODE, publisher=publisher_id)
or whatever way you want to identify that specific publisher (and setting related_name="" is a bad idea, because then you cannot make a reverse relationship at all).
Also, I would change the relationship of Publisher.publication to ForeignKey, because unless you have a situation where more than one Publisher is working together with another Publisher on the same article, you don't need M2M, you need O2M (one Publisher writes many publications/Translations).

Do a query through a foreignkey in Django

How do I travel through multiple foreign keys in Django? I've tried everything I can think of from the django docs, but I'm obviously missed something (extreme newbie). I have models for scientists, experiments, and theories.
If I want to look at a particular Theory (let's call it 'relativity') and get a list of all of the emails of scientists working on it (kept in the normal django user model), how do I do this?
class Experiment(models.Model)
experimenter = models.ForeignKey(Scientist)
theory = models.ForeignKey(Theory)
class Theory(models.Model)
name = models.CharField(max_length=100)
class Scientist(models.Model)
user = models.ForeignKey(User, unique=True)
institution = models.CharField(max_length=20, null=True, blank=True)
These are simplified versions of my models that I rewrote, so there are probably some errors in it, but the relationships are correct.
I've tried every kind of combinations of select_related(), get(), filter() but can't figure it out. Thanks in advance for your help!
User.objects.filter(scientist__experiment__theory__name=u'relativity')
Take a look at the Django documentation section about Lookups that span relationships. The net takeaway is:
To span a relationship, just use the field name of related fields across models, separated by double underscores, until you get to the field you want.
Ignacio's answer shows an example of using the double underscores on field names to span a relationship.
The other relevant portion of Django's documentation would be the Related objects section. Relationships in Django are asymmetrical in the way they are accessed. Forward/normal relationships are accessed as attributes of the models. Backward relationships are accessed:
Django also creates API accessors for the "other" side of the relationship -- the link from the related model to the model that defines the relationship. For example, a Blog object b has access to a list of all related Entry objects via the entry_set attribute: b.entry_set.all().

Django: Structure Django Model to allow Arbitrary Fieldtypes

I'd like to make a user profile app in Django (I know there are some that exist, thanks) and I'm wondering how you would structure the models to allow for an arbitrary combination fields in each sub-section.
As an example, the section 'education' may have a sub-section called 'Programming Experience', and the section 'personal info' may have a sub-section called 'favourites'.
Thinking in terms of a typical side bar navigation setup each section would be a header, and each sub-section would be a link to a form where the information can be manipulated.
Education
- Schooling
- Programming Experience
Personal Info
- Location
- Favourites
- Look a-likes
What I'd like to do is be able to add items to the sub-sections on an Arbitrary basis. Whatever the feel of the site calls for.
Maybe one site would benefit from photos of the school a user attended, while another might only need a description.
I'd like to use the admin interface to add these field types to the sub-section. So adding an item would present the choice of what type of information it is (image, video, text, etc) and what sub-section it's to be applied to.
I'd like to know how you would accomplish this; and more importantly, by jumping through as few hoops as possible.
Thanks.
Edit:
To hopefully clarify the question I'll provide a sample models.py file. This is just a quick moch-up to demonstrate the problem more accurately. I have two solutions in mind, and I think solution two will work better than solution one; but I'd also like to here what the SO community thinks and if they have any other solutions of their own.
**models.py**
class Section(models.Model):
"""
The root of categorization. Acts much like a header
"""
name = models.CharField(max_length=30)
description = models.CharField(max_length=255)
class SubSection(models.Model):
"""
The contents of each section. Contains many items of varying types as needed
by the site developer.
"""
name = models.CharField(max_length=30)
description = models.CharField(max_length=255)
section = models.ForeignKey(Section)
class Item(models.Model):
"""
I would like this to store the information here and have a foreign key to the
'SubSection' table. The problem is that there are a lot of different information
types that can be stored and I'd need a row for each type. Thus for each
entry most of the columns will be blank.
I'm thinking that it may be better to use this table as a pointer to another
table that actually contains the information. This will result in a lot of
tables but will eliminate waste.
"""
name = models.CharField(max_length=30)
description = models.CharField(max_length=255)
sub_section = models.ForeignKey(SubSection)
### Solution One
# Storing the info here results in a lot of wasted space and may not be all
# that flexible
image = models.ImageField()
text = models.CharField(max_length=255)
numeric = models.IntegerField()
time = models.TimeField()
# etc ...
### Solution Two
# Storing the field info results in more tables but allows for a better match
# of information and field type.
field_type = models.CharField(max_length=255)
field_properties = models.CommaSeparatedIntegerField(max_length=None)
### Solution Two Tables
# Solution two would require a table for each field type supported here, which
# is quite a few different types.
class ImageStorage(models.Model):
item = models.ForeignKey(Item)
information = models.ImageField()
class IntegerStorage(models.Model):
item = models.ForeignKey(Item)
information = models.IntegerField()
### etc ...
Just keep in mind it's targeted at user profiles. So a weight loss site may want the users current weight in the profile (numeric information) while a travel site may want a list of places visited (text information, could even use the IPAddressField I suppose). I just have no idea what will pop up so I'm trying to make it as generic as possible.
If I'm understanding you properly, the simplest way to do this would likely be a many-to-one relationship. I'm not sure if you wanted these on a per-user or per-site basis so I'll assume you want to customize it per-site (switching that is easy).
Create a table that looks something like this:
class Section(models.Model):
section = models.CharField()
sub-section = model.CharField()
site = models.ForeignKey(Site)
For multiple subsections that belong to the same section, simply give them the same primary section name so that you can query the DB for the accompanying subsections using that name.
Another way would be to use two tables to accomplish the same thing, but given the application I think this might be more appropriate.