Django model: Conversation between two users - python

I'm trying to create a Model which represents a conversation between two users (only two).
Can't figure out how to create two fields because users are equivalent.
class Conversation(models.Model):
user_one = ForeignKey('auth.User')
user_two = ForeignKey('auth.User')
class Meta:
unique_together = ('user_one','user_two')
Is this the best way I can design a model?
And then manager method:
def get_conversation(user_one,user_two):
c = Conversation.objects.filter(Q(user_one=user_one,user_two=user_two)|Q(user_one=user_one,user_two=user_two))
return c
Or is there a more comfortable way to handle such model? For example using ManyToManyField and check if there are two and only two users?:
users = ManyToManyField('auth.User')

Use the related_name field when you have more than 1 foreign key to the same model. Because you often don't care who specifically is user_one and user_two, you can simply make sure that user_one and user_two are consistent. In this case, I'm using the user's id field to say which user will be user_one and which will be user_two. This makes querying simpler because you don't need to do a query for the two pairs (A, B) and (B, A)
class Conversation(models.Model):
user_one = ForeignKey('auth.User', related_name="user_one")
user_two = ForeignKey('auth.User', related_name="user_two")
class Meta:
unique_together = ('user_one','user_two')
def clean(self):
# Ensure that user_one's id is always less than user_two's
if self.user_one and self.user_two and self.user_one.id > self.user_two.id:
(self.user_one, self.user_two) = (self.user_two, self.user_one)
#classmethod
def get(cls, userA, userB):
""" Gets all conversations between userA and userB
"""
if userA.id > userB.id:
(userA, userB) = (userB, userA)
return cls.objects.filter(user_one=userA, user_two=userB)

If you are using postgres you could use an ArrayField:
class Conversation(models.Model):
users = ArrayField(
ForeignKey('auth.User'),
size=2,
)
That would help with lookups. However note what the documentation currently says about the size parameter:
This is an optional argument. If passed, the array will have a maximum size as specified. This will be passed to the database, although PostgreSQL at present does not enforce the restriction.

Related

Peewee - Access an intermediary table easily

Say I have peewee models like so:
class Users(_BaseModel):
id = AutoField(primary_key=True, null=False, unique=True)
first_name = CharField(null=False)
last_name = CharField(null=False)
# Cut short for clarity
class Cohorts(_BaseModel):
id = AutoField(primary_key=True, null=False, unique=True)
name = CharField(null=False, unique=True)
# Cut short for clarity
class CohortsUsers(_BaseModel):
cohort = ForeignKeyField(Cohorts)
user = ForeignKeyField(Users)
is_primary = BooleanField(default=True)
I need to access easily from the user what cohort they are in and for example the cohort's name.
If a user could be in just one cohort, it would be easy but here, having it be many2many complicates things.
Here's what I got so far, which is pretty ugly and inefficient
Users.select(Users, CohortsUsers).join(CohortsUsers).where(Users.id == 1)[0].cohortsusers.cohort.name
Which will do what I require it to but I'd like to find a better way to do it.
Is there a way to have it so I can do Users.get_by_id(1).cohort.name ?
EDIT: I'm thinking about making methods to access them easily on my Users class but I am not really sure it's the best way of doing it nor how to go about it
If it do it like so, it's quite ugly because of the import inside the method to avoid circular imports
#property
def cohort(self):
from dst_datamodel.cohorts import CohortsUsers
return Users.select(Users, CohortsUsers).join(CohortsUsers).where(Users.id == self.id)[0].cohortsusers.cohort
But having this ugly method allows me to do Users.get_by_id(1).cohort easily
This is all covered in the documentation here: http://docs.peewee-orm.com/en/latest/peewee/relationships.html#implementing-many-to-many
You have a many-to-many relationship, where a user can be in zero, one or many cohorts, and a cohort may have zero, one or many users.
If there is some invariant where a user only has one cohort, then just do this:
# Get all cohorts for a given user id and print their name(s).
q = Cohort.select().join(CohortUsers).where(CohortUsers.user == some_user_id)
for cohort in q:
print(cohort.name)
More specific to your example:
#property
def cohort(self):
from dst_datamodel.cohorts import CohortsUsers
cohort = Cohort.select().join(CohortsUsers).where(CohortUsers.user == self.id).get()
return cohort.name

Hybrid property expression with JOIN

I'm fairly new to peewee, but have some strong background on SQLAlchemy (and all the vices that come with it). I'm trying to create a custom hybrid expression that correlates to a third (or even N) table. I'll try to demonstrate in an example (non-tested) code:
class BaseModel(Model):
class Meta:
database = database
class Person(BaseModel):
id = PrimaryKeyField(column_name="person_id")
name = CharField(max_length=255, column_name="person_name")
username = CharField(max_length=255, column_name="person_username")
class PersonTree(BaseModel):
id = PrimaryKeyField(column_name="person_tree_id")
name = CharField(max_length=255, column_name="person_tree_name")
code = CharField(max_length=255, column_name="person_tree_code")
person = ForeignKeyField(
column_name="person_id",
model=Person,
field="id",
backref="tree",
)
class Article(BaseModel):
id = PrimaryKeyField(column_name="article_id")
name = CharField(max_length=255, column_name="article_name")
branch = ForeignKeyField(
column_name="person_tree_id",
model=PersonTree,
field="id",
backref="articles",
)
#hybrid_property
def username(self):
"""
This gives me the possibility to grab the direct username of an article
"""
return self.branch.person.username
#username.expression
def username(cls):
"""
What if I wanted to do: Article.query().where(Article.username == "john_doe") ?
"""
pass
With the username hybrid_property on Article, I can get the username of the Person related to an Article using the PersonTree as a correlation, so far so good, but ... What if I wanted to "create a shortcut" to query all Articles created by the "john_doe" Person username, without declaring the JOINs every time I make the query and without relying on .filter(branch__person__username="john_doe")? I know it's possible with SA (to a great extent), but I'm finding this hard to accomplish with peewee.
Just for clarification, here's the SQL I hope to be able to construct:
SELECT
*
FROM
article a
JOIN person_tree pt ON a.person_tree_id = pt.person_tree_id
JOIN person p ON pt.person_id = p.person_id
WHERE
p.username = 'john_doe';
Thanks a lot in advance!
Hybrid properties can be used to allow an attribute to be expressed as a property of a model instance or as a scalar computation in a SQL query.
What you're trying to do, which is add multiple joins and stuff via the property, is not possible using hybrid properties.
What if I wanted to "create a shortcut" to query all Articles created by the "john_doe" Person username
Just add a normal method:
#classmethod
def by_username(cls, username):
return (Article
.select(Article, PersonTree, Person)
.join(PersonTree)
.join(Person)
.where(Person.name == username))

Prefetch Without Direct Relation Django

In Django is there a way to do a prefetch without having a direct foreign key relationship?
Or specifically, to do a prefetch based off the values of two columns?
I have the models
class ProductionType(models.Model ):
name = models.CharField(max_length=100,default="New Job Progress Type")
def __str__(self):
return self.name
def label_from_instance(self, obj):
return "{0}".format(obj.name)
class ProductionRecord(models.Model):
job = models.ForeignKey(Job,null=True,on_delete=models.SET_NULL,related_name="production_records")
type = models.ForeignKey(ProductionType,null=True)
done = models.IntegerField(default=1)
and then in another app
class ShopProgress(models.Model ):
job= models.ForeignKey(Job, on_delete=models.CASCADE, related_name="shop_progress")
total=models.IntegerField(null=True)
type=models.ForeignKey(ProductionType,on_delete=models.CASCADE)
#property
def done(self):
x = ProductionRecord.objects.values_list("done").filter(job=self.job, type=self.type).aggregate(models.Sum('done'))
t =x["done__sum"]
if t == None or t== 0:
t=0
self._done=t
return t
ProductionRecord and ShopProgress both reference a Job and a Production Type.
So the relation is dependent on these two fields, not a direct foreign key.
In this particular case, i would like for the query in "ShopProgress.done()" to be prefetched when querying ShopProgress.
A similar raw query would look roughly like
select a.id, a.type_id, a.total ,sum(b.done), a.job_id from DailyProgressReport_shopprogress a
left join production_productionrecord b on b.type_id=a.type_id and b.job_id = a.job_id
group by a.job_id, a.type_id
order by a.id
I hope the question is concise. If not, let me know and i will try to provide additional clarity.

Perform lookup and update within a single Django query

I have two models: MetaModel and RelatedModel. I want to include the result of a RelatedModel lookup within a MetaModel query, and I'd like to do this within a single DB call.
I've tried to define a 'subquery' QuerySet for use in the main query, but that hasn't worked - it's still making two queries to complete the operation.
Note: I can't use a traditional ForeignKey relationship because the profile_id field is not unique. Uniqueness is a combination of profile_id and channel. This is an aggregation table and profile_id is not guaranteed to be unique across multiple third-party channels.
Any suggestions?
Models:
class Channel(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(
max_length=25,
)
class MetaModel(models.Model):
profile_id = fields.IntegerField()
channel = fields.ForeignKey(Channel))
metadata = fields.TextField()
class RelatedModel(models.Model):
related_id = fields.IntegerField()
profile_id = fields.IntegerField()
channel = fields.ForeignKey(Channel))
Dummy data
channel = Channel("Web site A")
channel.save()
sample_meta = MetaModel(profile_id=1234, channel=channel)
sample_related = RelatedModel(profile_id=1234, related_id=5678, channel=channel)
Query:
# Create a queryset to filter down to the single record we need the `profile_id` for
# I've limited to the only field we need via a `values` operation
related_qs = RelatedAccount.objects.filter(
related_id=5678,
channel=channel
).values_list("profile_id", flat=True)
# I'm doing an update_or_create as there is other data to store, not included for brevity
obj, created = MetaModel.objects.update_or_create(
profile_id=related_qs.first(), # <<< This var is the dynamic part of the query
channel=channel,
defaults={"metadata": "Metadata is added to a new or existing record."}
)
Regarding your note on uniqueness, you can use unique_together option in Django as described here in the documentation.
class MetaModel(models.Model):
profile_id = fields.ForeignKey(RelatedModel)
channel = fields.ForeignKey(Channel)
metadata = fields.TextField()
class Meta:
unique_together = ('profile_id', 'channel')
Then you can change your query accordingly and should solve your problem.

Sorting for custom fields in models in django admin

I want to have sorting functionality for custom model field in django admin.
The code is similar to
class MyModel(models.Model):
first_name = models.CharField()
last_name = models.CharField()
def most_recent_mailing_date(self):
""" Return the most recent mailing date """
mailingHistories = self.mailinghistory_set.all()
if len(mailingHistories) != 0:
today = datetime.date.today()
mostRecentHistory = None
diff = -1
for mailingHistory in mailingHistories:
if mailingHistory.mailing_date < today and (diff == -1 or (today - mailingHistory.mailing_date) < diff):
mostRecentHistory = mailingHistory
diff = today - mostRecentHistory.mailing_date
if mostRecentHistory is None:
return "No Mailing History"
else:
return mostRecentHistory.mailing_date
else:
return "No Mailing History"
most_recent_mailing_date.admin_order_field = 'self.most_recent_mailing_date'
The field I want to order is most_recent_mailing_date.
It is a custom field.
Is it possible?
Thanks in advance!
I don't think that's possible. From the docs:
You have four possible values that can be used in list_display:
....
A string representing an attribute on the model. This behaves almost
the same as the callable, but self in this context is the model
instance. Here’s a full model example:
from django.db import models from django.contrib import admin
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')
Thus, your field is the fourth option. However:
A few special cases to note about list_display:
...
Usually, elements of list_display that aren’t actual database fields
can’t be used in sorting (because Django does all the sorting at the
database level).
...(goes on to describe exception that doesn't apply here).
Thus, you can only sort on actual database fields.
You can't use Django's order_by since it is applied at the database level. The database does not know anything about your python methods and properties.
However, You can do the ordering in Python
objects = MyModel.objects.all()
sorted(objects, key=lambda k: k.most_recent_mailing_date())
If you want reverse ordering,
objects = MyModel.objects.all()
sorted(objects, key=lambda k: k.most_recent_mailing_date(), reverse=True)
Advice
I think you should be consistent on your return type. If there are no mailing history, you can return some old date instead of returning a string.
I think you should consider using the #property decorator on your most_recent_mailing_date() so you can simply refer to it as instance.most_recent_mailing_date. This will make it somehow consistent on how you refer to your actual model fields.

Categories

Resources