Django: Are multiple Generic Relations in one Model bad design? - django

I have a model with multiple Generic Relations that has become very complicated to use in my templates. The model is a 'Gig' or musical event that takes place at a 'Venue' and/or a 'Festival' and has a 'Musician' and/or an 'Ensemble'.
Where it gets complicated is that each 'Gig' has a presenter, promoter and an agent. These are setup as generic relations to other models such as 'PresenterCompany'. A Presenter company could be a promoter, presenter, or agent, or all of them for the same gig. Here are the models (simplified for ref):
class Gig(models.Model):
description = models.CharField(max_length=100, blank=True)
date = models.DateTimeField()
venue = models.ForeignKey(Venue)
festival = models.ForeignKey(Festival, blank = True, null=True)
musician = models.ManyToManyField(Musician, blank=True)
ensembles = models.ManyToManyField(Ensemble, blank = True)
presenter_content_type = models.ForeignKey(ContentType,
limit_choices_to={"model__in": ("Individual", "ArtsOrganization",'Presenter', "BookingAgent","Festival", "OtherOrganization","PresenterCompany", "Venue")}, related_name = "Presenter Type", verbose_name = "Presenter",blank=True, null=True)
presenter_id = models.IntegerField(db_index=True, blank=True, null=True, verbose_name='Presenter ID')
presenter = generic.GenericForeignKey('presenter_content_type','presenter_id')
promoter_content_type = models.ForeignKey(ContentType,
limit_choices_to={"model__in": ("Individual", "ArtsOrganization","BookingAgent","Presenter", "Festival", "OtherOrganization","PresenterCompany", "Venue")}, related_name = "promotor", verbose_name='Promoter Type', blank=True, null=True)
promoter_id = models.IntegerField(db_index=True, blank=True, null=True, verbose_name='Promoter ID')
promoter = generic.GenericForeignKey('promoter_content_type','promoter_id')
agent_content_type = models.ForeignKey(ContentType,
limit_choices_to={"model__in": ("Individual", "BookingAgent")}, related_name="agent", verbose_name='Agent Type', blank=True, null=True)
agent_id = models.IntegerField(db_index=True, blank=True, null=True, verbose_name='Agent ID')
agent = generic.GenericForeignKey('agent_content_type','agent_id')
class PresenterCompany(models.Model):
name = models.CharField(max_length=70)
address =GenericRelation(Address)
presented_gig = GenericRelation('Gig',
content_type_field='presenter_content_type',
object_id_field='presenter_id',
related_name='presenter_presented_gig'
)
promoted_gig = GenericRelation('Gig',
content_type_field='promoter_content_type',
object_id_field='promoter_id',
related_name='presenter_promoted_gig'
)
booked_gig = GenericRelation('Gig',
content_type_field='promoter_content_type',
object_id_field='promoter_id',
related_name='presenter_booked_gig'
)
The main issue is that when I try to get all of the gigs for a presenter company, I have to write three different for loops for each role i.e. {% for gig in presentercompany.presented_gig.all %}, and so on... This seems like redundant code.
Is there a better way to structure this such as using intermediary models for presenter, promoter, and agent? Thanks for your advice!

Generic relationships can definitely be hard to deal with. I would only use them when there is no other option.
In your case, I see a couple other options. You could have a ManyToMany relationship between PresenterCompany and Gig using a through table to specify the type of relationship (https://docs.djangoproject.com/en/2.0/topics/db/models/#extra-fields-on-many-to-many-relationships):
class Gig(models.Model):
description = models.CharField(max_length=100, blank=True)
date = models.DateTimeField()
venue = models.ForeignKey(Venue)
festival = models.ForeignKey(Festival, blank=True, null=True)
musician = models.ManyToManyField(Musician, blank=True)
ensembles = models.ManyToManyField(Ensemble, blank=True)
class PresenterCompanyGigRelationship(models.Model):
gig = models.ForeignKey(Gig, on_delete=models.CASCADE)
presenter_company = models.ForeignKey(
'PresenterCompany', on_delete=models.CASCADE)
relationship = models.CharField(
max_length=10,
choices=(
('presenter', 'Presenter'),
('promoter', 'Promoter'),
('agent', 'Agent'),
))
class PresenterCompany(models.Model):
name = models.CharField(max_length=70)
git = models.ManyToManyField(Gig, through=PresenterCompanyGigRelationship)

Related

When I open the model, one field value is replaced automatically

Have a stupid issue: When I open the employee model (in django admin), one field (employee_type) value is replaced automatically, idk why...
Example: I create employee, define employee type as manager, save. In DB value is manager. After that, I open employee and see employee type as sewer and if I save, that value will saved in DB.
I created text choices for this field, and In DB field is defined as enum.
I tried to research the issue, value isn't used from DB, always first value from text choices is used.
By the way, I created same fields (enum in DB and text choices in the model) in other model. And all works properly.
How can I fix it???
Models.py with the issue:
class Employee(models.Model):
class EmployeeType(models.TextChoices):
SEWER = 'SEWER', _('Sewer')
MANAGER = 'MANAGER', _('Manager')
UNDEFINED = 'UNDEFINED', _('Undefined')
user = models.OneToOneField(User,
models.CASCADE,
db_column='user',
verbose_name=_('User'),
primary_key=True)
employee_type = models.CharField(db_column='Employee type',
verbose_name=_('Employee type'),
max_length=9,
choices=EmployeeType.choices,
default=EmployeeType.UNDEFINED)
phone = models.CharField(db_column='Phone',
verbose_name=_('Phone'),
max_length=255)
work_xp = models.IntegerField(db_column='Work XP',
verbose_name=_('Work XP'),
blank=True,
null=True)
Another one models.py. With same fields but without issues:
class Order(models.Model):
class OrderStatus(models.TextChoices):
CREATED = 'Created', _('Created')
CANCELLED = 'Cancelled', _('Cancelled')
IN_PROGRESS = 'In progress', _('In progress')
COMPLETED = 'Completed', _('Completed')
PASSED_TO_CLIENT = 'Passed to the client', _('Passed to the client')
RETURNED_FOR_REWORK = 'Returned for rework', _('Returned for rework')
class Urgency(models.TextChoices):
LOW = 'Low', _('Low urgency')
MEDIUM = 'Medium', _('Medium urgency')
HIGH = 'High', _('High urgency')
VERY_HIGH = 'Very high', _('Very high urgency')
class LabourIntensity(models.TextChoices):
LOW = 'Low', _('1-3 days')
MEDIUM = 'Medium', _('4-6 days')
HIGH = 'High', _('7-9 days')
VERY_HIGH = 'Very high', _('10+ days')
class PaymentStatus(models.TextChoices):
PENDING = 'Pending payment', _('Pending payment')
PREPAYMENT_MADE = 'Prepayment made', _('Prepayment made')
PAID = 'Paid', _('Paid')
id_service = models.ForeignKey(Service,
models.SET_NULL,
db_column='id_Service',
verbose_name=_('Service'),
blank=True,
null=True)
status = models.CharField(db_column='Status',
verbose_name=_('Status'),
max_length=20,
choices=OrderStatus.choices,
default=OrderStatus.CREATED)
payment_status = models.CharField(db_column='Payment status',
verbose_name=_('Payment status'),
max_length=15,
choices=PaymentStatus.choices,
blank=True,
null=True)
prepayment = models.DecimalField(db_column='Prepayment',
verbose_name=_('Prepayment'),
max_digits=19,
decimal_places=2,
blank=True,
null=True)
cost = models.DecimalField(db_column='Cost',
verbose_name=_('Cost'),
max_digits=19,
decimal_places=2,
blank=True,
null=True)
start_date = models.DateTimeField(db_column='Start date',
verbose_name=_('Start date'),
blank=True,
null=True)
end_date = models.DateTimeField(db_column='End date',
verbose_name=_('End date'),
blank=True,
null=True)
id_client = models.ForeignKey(Client,
models.SET_NULL,
db_column='id_Client',
verbose_name=_('Client'),
blank=True,
null=True)
id_employee = models.ForeignKey(Employee,
models.SET_NULL,
db_column='id_Employee',
verbose_name=_('Employee'),
blank=True,
null=True)
labour_intensity = models.CharField(db_column='Labour intensity',
verbose_name=_('Labour intensity'),
max_length=9,
choices=LabourIntensity.choices,
default=LabourIntensity.LOW)
urgency = models.CharField(db_column='Urgency',
verbose_name=_('Urgency'),
max_length=9,
choices=Urgency.choices,
default=Urgency.LOW)
materials = models.ManyToManyField(Material, through='OrderMaterials')
comment = models.TextField(db_column='Comment',
verbose_name=_('Comment'),
blank=True,
null=True)
I solved this issue.
In DB a type of field "employee_type" was defined as:
ENUM('Undefined','Sewer','Manager')
But the model has EmployeeType choices with uppercase.
Solution: I changed lowercase to uppercase of the values in the field type in DB:
ENUM('UNDEFINED','SEWER','MANAGER')
Now everything works fine.

Django : pre selection or tags. model relations

Django Version is 2.1.7
Hello, i have a OneToMany Relation and i ask my self if there is a possibility to make some kind of pre-selection (Tags or so?) for my Farmers?
Because not every Farmer has or wants Chickens or he is specialist in Cows only.
Means, right now, whenever i want to assign an individual Animal to a Farmer, i see all Farmers displayed in my Django Admin. With a growing Number of Farmers it gets confusing. So i thought to insert some Kind of Model Field in my Farmers Model... like chickens = true or not true and cows = true or not true or to introduce a new model for every species.
My Goal is, to assign a set of species to a every farmer. So that the Next time i want to add a chicken django shows only Farmers that will work with Chickens on their Farmland, it makes no sense to Display all Farmers, when some Farmers know that they handel only a specific set of species.
As a Newbie i would guess i have to make some new models for every Species with a ManyToMany Relation? So Farmers >< Species X, Y, Z < Indiviual Anmial.
Thanks
class Farmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
<...>
class Chickens(models.Model):
farmer = models.ForeignKey(Farmers, on_delete=models.CASCADE, null=True)
chickenname = models.CharField(max_length=100)
<...>
class Cows(models.Model):
farmer = models.ForeignKey(Farmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
<...>
class Rabbits(models.Model):
farmer = models.ForeignKey(Farmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
<...>
If we are using postgres as DB then arrayFieldlink
can be a good option for doing this job.
from django.contrib.postgres.fields import ArrayField
class Farmers(models.Model):
.... necessary fields
SAMPLE_CHOICES = (
('CHICKEN', 'CHICKEN'),
('COW, 'COW'),
('No Species', 'No Species')
.....
)
choices = ArrayField(
models.CharField(choices=SAMPLE_CHOICES, max_length=10, blank=True, default='No Species'),
)
Now whenever we need to filter on Farmer model based on choices we can do this like following
Farmer.objects.filter(choices__contains=['cow'])
Update
As you are using django-mysql database, following thing by django-mysql link here we can have field feature like ListField link and can easily achieve this.
class ChickenFarmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
class CowFarmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
class RabbitsFarmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
class Chickens(models.Model):
farmer = models.ForeignKey(ChickenFarmers, on_delete=models.CASCADE, null=True)
chickenname = models.CharField(max_length=100)
class Cows(models.Model):
farmer = models.ForeignKey(CowFarmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
class Rabbits(models.Model):
farmer = models.ForeignKey(RabbitsFarmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
'''
I think at this point this will give you best relief
'''

Django connected SQL queries with filters

Example:
class Room(models.Model):
assigned_floor = models.ForeignKey(Floor, null=True, on_delete=models.CASCADE)
room_nr = models.CharField(db_index=True, max_length=4, unique=True, null=True)
locked = models.BooleanField(db_index=True, default=False)
last_cleaning = models.DateTimeField(db_index=True, auto_now_add=True, null=True)
...
class Floor(models.Model):
assigned_building = models.ForeignKey(Building, on_delete=models.CASCADE)
wall_color = models.CharField(db_index=True, max_length=255, blank=True, null=True)
...
class Building(models.Model):
name = models.CharField(db_index=True, max_length=255, unique=True, null=True)
number = models.PositiveIntegerField(db_index=True)
color = models.CharField(db_index=True, max_length=255, null=True)
...
I want to output all rooms in a table sorted by Building.number.
Data which I want to print for each room:
Building.number, Building.color, Building.name, Floor.wall_color, Room.last_cleaning
Furthermore I want to allow optional filters:
Room.locked, Room.last_cleaning, Floor.wall_color, Building.number, Building.color
With one table it's no Problem for me, but I don't know how I archive this with three tables.
kwargs = {'number': 123}
kwargs['color'] = 'blue'
all_buildings = Building.objects.filter(**kwargs).order_by('-number')
Can you please help me? Do I need write raw SQL queries or can I archive this with the Django model query APIs?
I'm using the latest Django version with PostgreSQL.
No raw sql needed:
room_queryset = Room.objects.filter(assigned_floor__wall_color='blue')
^^
# A double unterscore declares the following attribute to be a field of the object referenced in the foregoing foreign key field.
for room in room_queryset:
print(room.assigned_floor.assigned_building.number)
print(room.assigned_floor.assigned_building.color)
print(room.assigned_floor.assigned_building.name)
print(room.assigned_floor.wall_color)
print(room.last_cleaning)

Querying from child of model given django inheritance and m2m link to parent

Among my models, I have Exercise which has a m2m link to Workout. I also have WorkoutPlan and LogBook which are types of Workouts. WorkoutPlan is where ideal workouts are stored. LogBook is where a user stores the workout they actually completed. They can also link a LogBook to a WorkoutPlan to indicate that the actual performance was connected to an original ideal plan.
class Exercise(NameDescModel):
muscles = models.ManyToManyField(Muscle, blank=True)
groups = models.ManyToManyField(Group, blank=True)
priority_score = models.DecimalField(max_digits=5, decimal_places=3, editable=False, default = 0)
frequency = models.IntegerField()
time_period = models.CharField(max_length=2, choices=TIME_PERIOD_CHOICES,default=WEEK)
last_p_calc_date = models.DateField("Date of Last Priority Recalculation", blank=True, null=True, default=datetime.now)
class Workout(NameDescModel):
exericises = models.ManyToManyField(Exercise, through='Measurement')
class WorkoutPlan(Workout):
priority_score = models.DecimalField(max_digits=5, decimal_places=3, editable=False, default = 0)
frequency = models.IntegerField()
time_period = models.CharField(max_length=2, choices=TIME_PERIOD_CHOICES,default=WEEK)
time_estimate = models.IntegerField()
last_p_calc_date = models.DateField("Date of Last Priority Recalculation", blank=True, null=True, default=datetime.now)
class LogBook(Workout):
workout_date = models.DateField(default=datetime.now)
notes = models.TextField(blank=True)
workout_plan = models.ForeignKey(WorkoutPlan, blank=True, null=True)
For a given exercise, I want to pull all of the WorkoutPlans that the exercise is in.
exercise_list = Exercise.objects.order_by('-last_p_calc_date')
for exercise in exercise_list:
print exercise
workout_list = []
for workout in exercise.workout_set.all():
workout_list.append(workout)
print list(set(workout_list))
print ""
I'm realizing that the list of workouts include both WorkoutPlans and LogBooks because exercise is attached to Workout, not to WorkoutPlans or LogBooks specifically.
How might I pull Workouts that are affiliated only to WorkoutPlans?
I think you've over-used inheritance here.
I guess you wanted to put the exercises field into a base model because WorkoutPlan and LogBook both have that field. But it seems like in reality WorkoutPlan and LogBook are different types of thing, rather than sub-types of Workout.
Possibly don't you need the exercises field on the LogBook model at all, since it has a foreign key to WorkoutPlan which seems a sensible place to record the exercises... unless you want to record the difference between the plan and exercises actually performed?
I would model it like this:
class Exercise(NameDescModel):
muscles = models.ManyToManyField(Muscle, blank=True)
groups = models.ManyToManyField(Group, blank=True)
priority_score = models.DecimalField(max_digits=5, decimal_places=3, editable=False, default = 0)
frequency = models.IntegerField()
time_period = models.CharField(max_length=2, choices=TIME_PERIOD_CHOICES,default=WEEK)
last_p_calc_date = models.DateField("Date of Last Priority Recalculation", blank=True, null=True, default=datetime.now)
class WorkoutPlan(Workout):
exercises = models.ManyToManyField(Exercise, through='Measurement')
priority_score = models.DecimalField(max_digits=5, decimal_places=3, editable=False, default = 0)
frequency = models.IntegerField()
time_period = models.CharField(max_length=2, choices=TIME_PERIOD_CHOICES,default=WEEK)
time_estimate = models.IntegerField()
last_p_calc_date = models.DateField("Date of Last Priority Recalculation", blank=True, null=True, default=datetime.now)
class LogBook(Workout):
exercises = models.ManyToManyField(Exercise, through='Measurement')
workout_date = models.DateField(default=datetime.now)
notes = models.TextField(blank=True)
workout_plan = models.ForeignKey(WorkoutPlan, blank=True, null=True)
You can then query either WorkoutPlans or LogBooks from an Exercise instance:
exercise_list = Exercise.objects.order_by('-last_p_calc_date')
for exercise in exercise_list:
print exercise
workout_list = exercise.workoutplan_set.all()
print ""

Django save two models with two foreign keys

One Article can have only 1 current Revision, but can have multiple revisions in history. Each Revision belongs to Article.
Models:
class Article(models.Model):
published = models.BooleanField(default=False)
current_revision = models.ForeignKey('Revision', related_name='current_revision', blank=False, default=None)
class Revision(models.Model):
article = models.ForeignKey('Article', null=True)
title = models.CharField('Title', max_length=250, blank=False, default='(no title)')
content = RedactorField('Content')
created_at = models.DateTimeField('Created at', auto_now=True)
slug = models.SlugField('Slug', blank=False, null=True)
View:
if article_form.is_valid() and revision_form.is_valid():
article = article_form.save(commit=False)
revision = revision_form.save()
article.current_revision = revision
article.save()
print(article)
revision.article = article
revision.save()
Is it okay to call save 3 times? Or is it possible to save 2 models with 2 saves?
It's ok to call save() 3 times, but, about your implementation.
I think, your models are not the best way to represent
One Article can have only 1 current Revision, but can have multiple revisions in history
Why not just, use a flag, to know if a revision is the current one, is better to check attributes than navigate between relationships
class Article(models.Model):
published = models.BooleanField(default=False)
class Revision(models.Model):
article = models.ForeignKey('Article', null=True)
title = models.CharField('Title', max_length=250, blank=False, default='(no title)')
content = RedactorField('Content')
created_at = models.DateTimeField('Created at', auto_now=True)
slug = models.SlugField('Slug', blank=False, null=True)
current = models.BooleanField(default=True)
#In your views
if article_form.is_valid() and revision_form.is_valid():
article = article_form.save()
#set prior current revision flag to False
last_current_revision = article.revision_set.filter(current=True)
last_current_revision.update(current=False)
revision = revision_form.save()
revision.article = article
revision.save()
In this way is less confusing.