Django, o2o/one-to-one modeling with multiple models - django

I have a model Job.
class Job(models.Model):
job_number = models.AutoField(primary_key=True)
date_opened = models.DateField()
staff_opened = models.ForeignKey(User, related_name="jobs_opened")
date_closed = models.DateField(blank=True, null=True)
staff_closed = models.ForeignKey(User, related_name="jobs_closed", db_index=True, blank=True, null=True)
date_promised = models.DateField(blank=True, null=True)
date_estimate = models.DateField(blank=True, null=True)
customer = models.CharField("Customer", max_length=50, db_index=True)
slug = models.SlugField(max_length=60, unique=True)
And I also have a number of different type of jobs that hold more information, depending on what it is:
class WorkshopJob(Job):
job = models.OneToOneField(Job, parent_link=True)
invoice_number = models.CharField("Invoice Number", max_length=30, blank=True)
part = models.ForeignKey(PartNumber)
serial_number = models.CharField(max_length=20, blank=True)
and
class EngineeringJob(Job):
job = models.OneToOneField(Job, parent_link=True)
work_order = models.CharField(max_length=30)
reported_fault = models.TextField()
findings = models.TextField(blank=True)
work_performed = models.TextField(blank=True)
Any particular Job can only have one Engineering Job, one Workshop Job - but it can have one of each, too.
I never instantiate a Job on it's own - there is no AddJob view or page - only the subclasses.
The part I'm struggling with is the link - if I am viewing the detail of one subclass, how can I "add" another type of Job to the same Job?
IE if the I had an engineering job with job_id=1, how do I "pass" the job_id=1 to the new Workshop Job in the view?
I've tried adding get_initial(self) to the views, but it isn't working for me.

I think I'm going to rejig my set up.
Previously I had urls like this:
url(r'^workshop/add/$', views.WorkshopJobAdd.as_view(), name='wsjob_add'),
url(r'^workshop/(?P<slug>[-\w]+)/$', views.WorkshopJobDetail.as_view(), name='wsjob_detail'),
url(r'^workshop/(?P<slug>[-\w]+)/edit/$', views.WorkshopJobEdit.as_view(), name='wsjob_edit'),
url(r'^workshop/(?P<slug>[-\w]+)/invoice/$', views.WSJInvoice.as_view(), name='wsj_invoice'),
url(r'^engineering/add/$', views.EngineeringJobAdd.as_view(), name='wsjob_add'),
url(r'^engineering/(?P<slug>[-\w]+)/$', views.EngineeringJobDetail.as_view(), name='wsjob_detail'),
url(r'^engineering/(?P<slug>[-\w]+)/edit/$', views.EngineeringJobEdit.as_view(), name='wsjob_edit'),
url(r'^engineering/(?P<slug>[-\w]+)/invoice/$', views.EngineeringInvoice.as_view(), name='wsj_invoice'),
And was trying to pass the job_id between views.
But I think the easiest/sensible solution to my problem is to change the urls to something more like:
url(r'^job/(?P<slug>[-\w]+)/workshop/add/$', views.WorkshopJobAdd.as_view(), name='wsjob_add'),
url(r'^job/(?P<slug>[-\w]+)/workshop/$', views.WorkshopJobDetail.as_view(), name='wsjob_detail'),
url(r'^job/(?P<slug>[-\w]+)/workshop/edit/$', views.WorkshopJobEdit.as_view(), name='wsjob_edit'),
url(r'^job/(?P<slug>[-\w]+)/workshop/invoice/$', views.WSJInvoice.as_view(), name='wsj_invoice'),
And then adding a new subclassed model to an existing Job would just be a matter of using the slug. I was making things far to hard for myself.

I think this is another case of "CBVs aren't always the best solution".
Which is not the end of the world - I think I do wish for an easy way to tell when it's the case though :\

Related

django multilevel nested formsets

django provide inline formset which allow to 3rd level of nesting, but I need much more complex nesting. It should be fully dynamic, so I can go one to one on each level, but it could be one to many on each level. So far I have this only, but could be expanded for additional sublevels.
class Srts(models.Model):
data = models.CharField(max_length=10, blank=True, null=True)
class Volume(models.Model):
srts = models.ForeignKey('Srts', on_delete=models.CASCADE)
name = models.CharField(max_length=120, blank=True, null=True)
class Qtree(models.Model):
volume = models.ForeignKey('Volume', on_delete=models.CASCADE)
name = models.CharField(max_length=120)
class Server(models.Model):
qtree = models.ForeignKey('Qtree', on_delete=models.CASCADE)
hostname = models.CharField(max_length=120, blank=True, null=True)
class CifsPermission(models.Model):
qtree = models.ForeignKey('Qtree', on_delete=models.CASCADE)
group = models.CharField(max_length=30, blank=True, null=True, default='None')
permission = models.CharField(max_length=30, blank=True, null=True, default='None')
I have been googling a lot last days, but there is not much.
Some examples
django-nested-inline-formsets-example -that basic only 3rd level
Django-better forms -could handle multiple forms on one submit, but not formsets
django-nested-inline -only for admin page
Shoudl be the way to work with not model related form , then do some separation and appropriate logic and then save it to models?
can't add image, some sever error ocured, so giving the link directly
https://imgur.com/a/NQBR6tJ
I would like to to something simular over normal view, not admin view.

Django Many to Many Data Duplication?

Background
I'm storing data about researchers. eg, researcher profiles, metrics for each researcher, journals they published in, papers they have, etc.
The Problem
My current database design is this:
Each Researcher has many journals (they published in). The journals have information about it.
Likewise for Subject Areas
But currently, this leads to massive data duplication. Eg, the same journal can appear many times in the Journal table, just linked to a different researcher, etc.
Is there any better way to tackle this problem? Like right now, I have over 5000 rows in the journal column but only about 1000 journals.
Thank you!
EDIT: This is likely due to the way im saving the models for new data (mentioned below). Could anyone provide the proper way to loop and save hashes to models?
Model - Researcher
class Researcher(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
scopus_id = models.BigIntegerField(db_index=True) # Index to make searches quicker
academic_rank = models.CharField(max_length=100)
title = models.CharField(max_length=200,default=None, blank=True, null=True)
salutation = models.CharField(max_length=200,default=None, blank=True, null=True)
scopus_first_name = models.CharField(max_length=100)
scopus_last_name = models.CharField(max_length=100)
affiliation = models.CharField(default=None, blank=True, null=True,max_length = 255)
department = models.CharField(default=None, blank=True, null=True,max_length = 255)
email = models.EmailField(default=None, blank=True, null=True)
properties = JSONField(default=dict)
def __str__(self):
return "{} {}, Scopus ID {}".format(self.scopus_first_name,self.scopus_last_name,self.scopus_id)
Model - Journal
class Journal(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
researchers = models.ManyToManyField(Researcher)
title = models.TextField()
journal_type = models.CharField(max_length=40,default=None,blank=True, null=True)
abbreviation = models.TextField(default=None, blank=True, null=True)
issn = models.CharField(max_length=50, default=None, blank=True, null=True)
journal_rank = models.IntegerField(default=None, blank=True, null=True)
properties = JSONField(default=dict)
def __str__(self):
return self.title
How I'm currently saving them:
db_model_fields = {'abbreviation': 'Front. Artif. Intell. Appl.',
'issn': '09226389',
'journal_type': 'k',
'researchers': <Researcher: x, Scopus ID f>,
'title': 'Frontiers in Artificial Intelligence and Applications'}
# remove researchers or else create will fail (some id need to exist error)
researcher = db_model_fields["researchers"]
del db_model_fields["researchers"]
model_obj = Journal(**db_model_fields)
model_obj.save()
model_obj.researchers.add(researcher)
model_obj.save()
Here is how it works :
class Journal(models.Model):
# some fields
class Researcher(models.Model):
# some fields
journal = models.ManyToManyField(Journal)
Django gonna create a relation table :
Behind the scenes, Django creates an intermediary join table to represent the many-to-many relationship
So you'll have many rows in this table, which is how it works, but journal instance and researcher instance in THEIR table will be unique.
Your error is maybe coming from how you save. Instead of :
model_obj = Journal(**db_model_fields)
model_obj.save()
Try to just do this:
model_obj = Journal.objects.get_or_create(journal_id)
This way you'll get it if it already exists. As none of your fields are unique, you're creating new journal but there's no problem cause django is generating unique ID each time you add a new journal.

RelatedManager not returning excepted results

I am currently working on a django app where I have two models:
class Job(models.Model):
def __unicode__(self):
return self.job_number
area = models.ForeignKey(Area)
customer = models.ForeignKey(Customer)
job_address = models.ForeignKey(Address, null=True)
job_status = models.ForeignKey(JobStatus)
surveyor = models.ForeignKey(Employee, null=True)
job_dates = models.OneToOneField(JobDates, null=True)
job_title = models.CharField(max_length=50, null=False)
job_number = models.CharField(max_length=15, null=False, unique=True)
deposit_amount = models.DecimalField(null=True, decimal_places=2, max_digits=10)
is_deposit_paid = models.BooleanField(default=False)
is_deposit_required = models.BooleanField(default=False)
particular = models.SmallIntegerField(null=True)
description = models.TextField(null=True)
surveyor_note = models.TextField(null=True)
contractor_note = models.TextField(null=True)
class Picture(models.Model):
def __unicode__(self):
return self.file_name
job = models.ForeignKey(Job, related_name='picture_job_set')
url = models.CharField(max_length=450, null=False, unique=True)
file_name = models.CharField(max_length=50, null=False)
is_main_pic = models.BooleanField(default=False)
title = models.CharField(max_length=50, null=True)
description = models.TextField(null=True)
date_uploaded = models.DateTimeField(null=False)
uploaded_by = models.ForeignKey(Employee, null=False)
So it is a one to many relationship where each job can have many pictures associated with it. Now in the picture model, I have the "is_main_pic" column which is used to display a picture in a listview and when they click on it, it goes to details and shows the rest of the pictures. Now my problem is whenever I try to filter to just get the main picture within each job in the list view, it returns all the pictures for each job in the list view if the condition is met for any of the pictures. My query looks like this:
Jobs.objects.filter(picture_job_set__is_main_pic=True).prefetch_related('picture_job_set')
Now when I go to access the picture_job_set, it contains all pictures associated with a job, even if their "is_main_pic" column is false.
I would like to be able to in the template view just to call first on the picture_job_set as there will always only be one main picture for each job. As of right now I am able to get around this by simply having a for loop in the template with an if statement to determine which is the main pic but this seems messy and inefficient.
Am I misunderstanding how RelatedManager works? Do I have to call two queries to achieve this? Coming from a ASP MVC background, I could achieve this with something like:
List<JobIndexViewModel> viewModel = await db.Jobs
.Select(x => new JobIndexViewModel
{
JobID = x.JobID,
JobTitle = x.JobTitle,
JobNumber = x.JobNumber,
BidExpireDate = x.JobDates.BidExpireDate,
JobPostedDate = x.JobDates.JobPostedDate,
City = x.Address.City,
PictureUrl = x.Pictures.Where(t => t.IsMainPic == true).Select(t => t.Url).FirstOrDefault()
}).ToListAsync();
I, playing django now, interprete your query as:
Give me all Jobs that has a picture from picture_job_sets, inasmuch as
one of the pictures is marked as main picture.
So like you said, it returns all the pictures for each job in the list view if the condition is met for any of the pictures.
Quoting you: there will always only be one main picture for each job. If there will always be a main picture, you can easily start the query from the pictures:
Picture.objects.filter(is_main_pic=True).select_related('Job')
Then you can iterate over each picture and do .job on each to get the related job.
One last thing though, I suspect you have this problem because of your design. Won't you have a simpler design if you move a main picture as a field to your Job model?

Django ModelForm or Form

i am totally new to both Python and Django,so please excuse me if this question is a bit simpleton.
I am writing a small app to track the scores of a Billiards match. I don't need to explain all the details, but the basic objects involved are:
Team has Players
Match is between two Teams (home and away)
Match has a collection of Games.
Each Game is between two Players (one from each team), excluding players who have already played in the Match.
I have made the following models:
class Team(models.Model):
team_id = models.IntegerField(unique=True, max_length=5, blank=False,validators=[validate_five_digits])
name = models.CharField(max_length=50, blank=False, null=False)
class Player(models.Model):
id = models.IntegerField(unique=True, max_length=5, blank=False,validators=[validate_five_digits])
team = models.ForeignKey(Team, blank=True, null=True)
first_name = models.CharField(max_length=50, blank=False, null=False)
last_name = models.CharField(max_length=50, blank=False, null=False)
alias_name = models.CharField(max_length=50, blank=True, null=True)
current_handicap = models.IntegerField()
class Match(models.Model):
date = models.DateField(blank=False, null=False)
location = models.CharField(max_length=255, blank=True, null=True)
table_size = models.CharField(max_length=50, blank=True, null=True)
home_team = models.ForeignKey(Team, related_name='home_team', blank=True, null=True)
away_team = models.ForeignKey(Team, related_name='away_team', blank=True, null=True)
class Game(models.Model):
match = models.ForeignKey(Match, blank=False, null=False)
match_sequence = models.IntegerField(blank=True, null=True)
player1 = models.ForeignKey(Player,related_name='player1', blank=False, null=False)
player2 = models.ForeignKey(Player,related_name='player2', blank=False, null=False)
player1_handicap = models.IntegerField(null=True, blank=True)
player2_handicap = models.IntegerField(null=True, blank=True)
I have successfully made Views and ModelForms to add/edit Teams, Players and Matches.
The list of Matches is displayed in a table, with 1 match per row....and now I want to put a button to add a new Game.
My plan is to do so by having the button go to a url that looks like this:
game/new/?match_id=1 (or something like that)
Now for the part where i am confused.....When you go to add a new game, i want to display 3 choice fields, and only 3 choice fields.
First Choice field should display Players from Home Team that have not yet played a game in this match
Second Choice field should display Players from Away Team that have not yet played a game in the match
Third Choice Field would only have two choices (Home and Away)....and would indicate which player gets to shoot first.
Then, when user clicks Submit it needs to create a Game() object with the match_id from the query string, the next sequence number for the match, and Player1 = either home or away player, based on 3rd choice field.
I am totally confused about how this should be done....should i be using a forms.Form or a ModelForm?
Any suggestions or skeleton code to clue me in?
Thanks in advance for help with such a newbie question!
Paul
What type of form class to use when building a Django app is a simple problem. If you're editing a model, use a ModelForm, otherwise use a Form.
If you need to limit choices or alter choices based on other data, you can still do that with a ModelForm by overriding the form's __init__()
Using a ModelForm will reduce the amount of code necessary to add the Game instance, because a ModelForm already knows how to create or update an instance of Game. You'd have to handle that yourself if you were just using a Form class.

Django model: manytomany with more than one object

I have an Event model. Events can have many 'presenters'. But each presenter can either 1 of 2 different types of profiles. Profile1 and Profile2. How do I allow both profiles to go into presenters?
This will be 100% backend produced. As to say, admin will be selecting "presenters".
(Don't know if that matters or not).
class Profile1(models.Model):
user = models.ForeignKey(User, null=True, unique=True)
first_name = models.CharField(max_length=20, null=True, blank=True)
last_name = models.CharField(max_length=20, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
about = models.TextField(null=True, blank=True)
tags = models.ManyToManyField(Tag, null=True, blank=True)
country = CountryField()
avatar = models.ImageField(upload_to='avatars/users/', null=True, blank=True)
score = models.FloatField(default=0.0, null=False, blank=True)
organization = models.CharField(max_length=2, choices=organizations)
class Profile2(models.Model):
user = models.ForeignKey(User, null=True, unique=True)
first_name = models.CharField(max_length=20, null=True, blank=True)
last_name = models.CharField(max_length=20, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
about = models.TextField(null=True, blank=True)
tags = models.ManyToManyField(Tag, null=True, blank=True)
country = CountryField()
avatar = models.ImageField(upload_to='avatars/users/', null=True, blank=True)
score = models.FloatField(default=0.0, null=False, blank=True)
...
class Event(models.Model):
title = models.CharField(max_length=200)
sub_heading = models.CharField(max_length=200)
presenters = ManyToManyField(Profile1, Profile2, blank=True, null=True) ?
...
# I've also tried:
profile1_presenters = models.ManyToManyField(Profile1, null=True, blank=True)
profile2_presenters = models.ManyToManyField(Profile2, null=True, blank=True)
# is there a better way to accomplish this?...
I think you have a desing problem here. In my opinion, you must think what is a Presenter and what's the different between a Presenter with "profile 1" and with "profile 2". What are you going to do with this models? Are you sure there are just two profiles? Is there any chance that, in some time from now, a different profile ("profile 3") appears? And profile 4? and profile N?
I recommend you to think again about your models and their relations. Do NOT make this decision thinking of how difficul/easy will be to handle these models from django admin. That's another problem and i'll bet that if you think your models a little bit, this won't be an issue later.
Nevertheless, i can give you some advice of how to acomplish what you want (or i hope so). Once you have think abount how to model these relations, start thinking on how are you going to write your models in django. Here are some questions you will have to answer to yourself:
Do you need one different table (if you are going to use SQL) per profile?
If you cannot answer that, try to answer these:
1) What's the difference between two different profiles?
2) Are there more than one profile?
3) Each presenter have just one profile? What are the chances that this property changes in near future?
I don't know a lot about what you need but i think the best option is to have a model "Profile" apart of your "Presenter" model. May be something like:
class Profile(models.Model):
first_profile_field = ...
second_profile_field = ...
# Each presenter have one profile. One profile can "represent"
# to none or more presenters
class Presenter(models.Model):
first_presenter_field = ....
second_presenter_field = ....
profile = models.ForeignKey(Profile)
class Event(models.Model):
presenters = models.ManyToManyField(Presenter)
....
This is just an idea of how i imagine you could design your model. Here are some links that may help you once you have design your models correctly and have answered the questions i made to you:
https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance
https://docs.djangoproject.com/en/dev/misc/design-philosophies/#models
http://www.martinfowler.com/eaaCatalog/activeRecord.html
And to work with the admin once you decide how your design will be:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/
EDIT:
If i'm not wrong, the only difference between profile 1 and 2 fields is the "organization" field. Am i right? So i recommend you to merge both models since they are almost the same. If they have different methods, or you want to add different managers or whatever, you can use the proxy option of django models. For example, you can do this:
class Profile(models.Model):
#All the fields you listed above, including the "organization" field
class GoldenProfile(models.Model):
#you can define its own managers
objects = GoldenProfileManager()
....
class Meta:
proxy = True
class SilverProfile(models.Model):
....
class Meta:
proxy = True
This way, you can define different methods or the same method with a different behaviour in each model. You can give them their own managers, etcetera.
And the event class should stay like this:
class Event(models.Model):
title = models.CharField(max_length=200)
sub_heading = models.CharField(max_length=200)
presenters = ManyToManyField(Profile, blank=True, null=True)
Hope it helps!