Django's ManyToMany Relationship with Additional Fields - database

I want to store some additional information in that, automatically created, ManyToMany join-table. How would I do that in Django?
In my case I have two tables: "Employees" and "Projects". What I want to store is how much each of the employees receives per hour of work in each of the projects, since those values are not the same. So, how would I do that?
What occurred to me was to, instead of the method "ManyToManyField", create explicitly a third class/table to store those new informations and to set its relationship with "Employees" and "Projects" using the "ForeignKey" method. I'm pretty sure it will work, but is this the best approach?

Here is example of what you want to achieve:
http://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
In case link ever breaks:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)

Related

How to sort query set (or array) with more than one type of elements (classes) by common attribute using Django Python

I need help with creating a query set (or array) that as more than one type of elements (different classes) by a common attribute.
here are the classes:
class aaa(models.Model):
fd = models.CharField(max_length=100, unique=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=True, auto_now=False)
#more attributes...
def __str__(self):
return self.fd
class bbb(models.Model):
fd = models.CharField(max_length=100)
date = models.DateTimeField(blank=True, null=True)
#more attributes...
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=True, auto_now=False)
class Meta:
ordering = ["-timestamp"]
def __str__(self):
return self.fd
I want to sort all this elements by the attribute "timestamp" (that is the same in the two classes).
So far, this is what i got:
my_objects = (aaa.objects.all(),bbb.objects.all())
That creates the query set of all the elements. I prefer the query set way, if its better to use array or some other structure please let me know.
thanks!
Use the pipe to merge querysets:
combined = qs1 | qs2 | qs3
But this wont work for querysets from distinct models, at least not if they have diffferent fields. You will get an Cannot combine queries on two different base models. error.
A possible solution is to use itertools.chain, like in this blog post, which i quote here for reference.
from itertools import chain
result_lst = list(chain(queryset1, queryset2))
Now, you can sort the resulting list by any common field, e.g. creation date
from operator import attrgetter
result_lst = sorted(
chain(queryset1, queryset2),
key=attrgetter('created_at'))

Django: overriding default field names in ManyToManyField

Good day,
Using a ManyToManyField as in the example below causes django to automatically create a Model called country_region with fields country_id and region_id, which is very cool. But what if the region_country database table (for reasons beyond the scope of this thread) has field names other than country_id and region_id?
Django generates the field names in the many-to-many table based on the model names of the related tables, so the only way I have found to achieve this is to change the two Model names. But that forces me to have model names that I don't want! How can I tell django what field names to use in the automatic many-to-many Model?
I have tried explicitly specifying the many-to-many Model using the through= keyword argument of the ManyToManyField, and this works, but then forms based on the Region Model do not allow saving...
Any advice greatly appreciated.
Thanks,
Randal
class Country(models.Model):
country_id = models.AutoField(primary_key=True)
country_name = models.CharField(max_length=200)
class Meta:
managed = False
db_table = 'country'
def __unicode__(self):
return '%s' % (self.country_name)
class Region(models.Model):
region_id = models.AutoField(primary_key=True)
region_name = models.CharField(max_length=200)
region = models.ManyToManyField(Country, db_table='region_country')
class Meta:
managed = False
db_table = 'region'

Django, Direction of ManyToMany relations

I can't find the logic to which of the two related models should have the M2M field? Here is an example from the Django tutorial
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
what difference would it make if I define the M2M field in Person instead of Group ?
class Person(models.Model):
name = models.CharField(max_length=128)
groups = models.ManyToManyField(Person, through='Membership')
class Group(models.Model):
name = models.CharField(max_length=128)
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Technically, you can add a ManyToManyField on either model and get equivalent results, so the decision usually comes down to which model it makes more sense in, and that's usually a very subjective decision.
I usually decide by determining which model the relationship is more integral to: is the set of members more important to a Group than the set of groups is to the Person? If so, the relationship field should go on the Group model.

How to create a query for matching keys?

I use the key of another User, the sponsor, to indicate who is the sponsor of a User and it creates a link in the datastore for those Users that have a sponsor and it can be at most one but a sponsor can sponsor many users like in this case ID 2002 who sponsored three other users:
In this case this query does what I want: SELECT * FROM User where sponsor =KEY('agtzfmJuYW5vLXd3d3ILCxIEVXNlchjSDww') but I don't know how to program that with python, I can only use it to the datastore. How can I query by key when I want to match the set of users who has the same user as key in the same field? A user in my model can have at most one sponsor and I just want to know who a particular person sponsored which could be a list of users and then they sponsored users in their turn which I also want to query on.
The field sponsor is a key and it has a link to the sponsor in the datastore. I set the key just like user2.sponsor = user1.key and now I want to find all that user1 sponsored with a query that should be just like
User.All().filter('sponsor = ', user1.key)
but sponsor is a field of type key so I don't know how to match it to see for example a list a people the active user is a sponsor for and how it becomes a tree when the second generation also have links. How to select the list of users this user is a sponsor for and then the second generation? When i modelled the relation simply like u1=u2.key ie user2.sponsor=user1.key. Thanks for any hint
The following workaround is bad practice but is my last and only resort:
def get(self):
auser = self.auth.get_user_by_session()
realuser = auth_models.User.get_by_id(long( auser['user_id'] ))
q = auth_models.User.query()
people = []
for p in q:
try:
if p.sponsor == realuser.key:
people.append(p)
except Exception, e:
pass
if auser:
self.render_jinja('my_organization.html', people=people, user=realuser,)
Update
The issues are that the keyproperty is not required and that Guido Van Rossum has reported this as a bug in the ndb when I think it's a bug in my code. Here's what I'm using now, which is a very acceptable solution since every real user in the organization except possibly programmers, testers and admins are going the be required to have a sponsor ID which is a user ID.
from ndb import query
class Myorg(NewBaseHandler):
#user_required
def get(self):
user = auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id']))
people = auth_models.User.query(auth_models.User.sponsor == user.key).fetch()
self.render_jinja('my_organization.html', people=people,
user=user)
class User(model.Expando):
"""Stores user authentication credentials or authorization ids."""
#: The model used to ensure uniqueness.
unique_model = Unique
#: The model used to store tokens.
token_model = UserToken
sponsor = KeyProperty()
created = model.DateTimeProperty(auto_now_add=True)
updated = model.DateTimeProperty(auto_now=True)
# ID for third party authentication, e.g. 'google:username'. UNIQUE.
auth_ids = model.StringProperty(repeated=True)
# Hashed password. Not required because third party authentication
# doesn't use password.
password = model.StringProperty()
...
The User model is an NDB Expando which is a little bit tricky to query.
From the docs
Another useful trick is querying an Expando kind for a dynamic
property. You won't be able to use class.query(class.propname ==
value) as the class doesn't have a property object. Instead, you can
use the ndb.query.FilterNode class to construct a filter expression,
as follows:
from ndb import model, query
class X(model.Expando):
#classmethod
def query_for(cls, name, value):
return cls.query(query.FilterNode(name, '=', value))
print X.query_for('blah', 42).fetch()
So try:
form ndb import query
def get(self):
auser = self.auth.get_user_by_session()
realuser = auth_models.User.get_by_id(long( auser['user_id'] ))
people = auth_models.User.query(query.FilterNode('sponsor', '=', realuser.key)).fetch()
if auser:
self.render_jinja('my_organization.html', people=people, user=realuser,)
Option #2
This option is a little bit cleaner. You subclass the model and pass it's location to webapp2. This will allow you to add custom attributes and custom queries to the class.
# custom_models.py
from webapp2_extras.appengine.auth.models import User
from google.appengine.ext.ndb import model
class CustomUser(User):
sponsor = model.KeyProperty()
#classmethod
def get_by_sponsor_key(cls, sponsor):
# How you handle this is up to you. You can return a query
# object as shown, or you could return the results.
return cls.query(cls.sponsor == sponsor)
# handlers.py
def get(self):
auser = self.auth.get_user_by_session()
realuser = custom_models.CustomUser.get_by_id(long( auser['user_id'] ))
people = custom_models.CustomUser.get_by_sponsor_key(realuser.key).fetch()
if auser:
self.render_jinja('my_organization.html', people=people, user=realuser,)
# main.py
config = {
# ...
'webapp2_extras.auth': {
# Tell webapp2 where it can find your CustomUser
'user_model': 'custom_models.CustomUser',
},
}
application = webapp2.WSGIApplication(routes, config=config)

Django Models - FK default to the last entered record (by the logged in user)

I have 2 models in question. JobRecord of which has many Activity(ies), the fk, of course, placed in the Activity model.
To aid data entry, I'd like to default the fk to JobRecord, within a new Activity, to the 'JobRecord fk of the last entered Activity, by the logged in User (bonus points!)'
Here's the model:
class Activity(models.Model):
"""
Record multiple activities against a JobRecord
"""
start = models.TimeField(blank=True, null=True)
end = models.TimeField(blank=True, null=True)
job_record = models.ForeignKey(JobRecord) # editable=False)
task = models.ForeignKey(Task, blank=True, null=True) # show only tasks found in project
flora = models.ManyToManyField(Species, blank=True, null=True, help_text='Optional subject flora.')
memo = models.TextField(blank=True, null=True)
all_operators = models.BooleanField(help_text="Check to include all operators from the parent job record")
operators = models.ManyToManyField(User, null=True, blank=True)
duration = models.DecimalField(blank=True, null=True, max_digits=7, decimal_places=2)
class Meta:
#order_with_respect_to = 'job_record' # not working?
ordering = ['-job_record__date', 'job_record', 'start']
Thanks!
As a very first step, you should store in some model the last activity for each user.
class ActivityLog(models.Model)
user = models.ForeignKey(User)
job_record = models.ForeignKey(JobRecord)
Then in the view, you can retrieve the activity by some simple query:
last_activity_data = ActivityLog.objects.filter(user=request.user)
and use it in initializing the form for the model:
form = ActivityForm(job_record=last_activity_data.job_record)
finally, remember to update the log when saving the Activity.
Note: I have omitted all error checking; for instance, you should take care of the case where there are no last_job_record's, but it should be quite trivial.
Finally, you could also use a ManyToManyField to the Activity, and then using Django's API; it would be perfectly equivalent (e.g., it would create a table very similar to this one behind the scenes), but I think the semantic would be slightly different and for this reason would I prefer the explicit solution.

Resources