Design an observation pattern in Django Rest Framework - python

I want to implement something like the pattern which introduced in this answer. For example I have four models like this:
class Protperty(models.Model):
property_type = models.CharField(choices=TYPE_CHOICES, default='float', max_length=100)
property_name = models.CharField(max_length=100)
class FloatProperty(models.Model):
property_id = models.ForeignKey(Property, related_name='value')
value = models.FloatField(default=0.0)
class IntProperty(models.Model):
property_id = models.ForeignKey(Property, related_name='value')
value = models.IntField(default=0)
class StringProperty(models.Model):
property_id = models.ForeignKey(Property, related_name='value')
value = models.CharField(max_lenght=100, blank=True, default='')
After defining these classes, I do not know how I must implement serializer or view classes. For example for writing serializer I want to put a field value, which must be set depend of type of object currently is serialized or deserialilzed(property_type defines it).
I am new to django and rest framework too, please give me some suggestions for implementing such models and serializers.
Edit:
In general I want to construct some models that using them I able to store run time defining property with different values and able to query them further. For example I have a store and it has different goods which each one has specific property and I want to query them using a specific property.

Related

How to allow boolean True on just one model in table in Django?

I've got a model where I would like there to be able to have one set as the 'app default'.
In this model I added a field named is_app_default in order to help accommodate this.
class TreeLevel(models.Model):
id = models.BigAutoField(primary_key=True)
short_description = models.CharField(max_length=200)
long_description = models.TextField()
is_app_default = models.BooleanField(default=False)
class Meta:
verbose_name = "Tree Level"
verbose_name_plural = "Tree Levels"
class Layer(models.Model):
id = models.BigAutoField(primary_key=True)
tree_levels = models.ManyToManyField(TreeLevel)
description = models.TextField()
class Meta:
verbose_name = "Layer"
verbose_name_plural = "Layers"
The Layer model links to TreeLevel with an m2m. Ultimately I would like the is_app_default TreeLevel automatically added to every Layer m2m relationship - which is why there can only be one TreeLevel with is_app_default set as True.
My potential solution(s):
Users with admin may be creating new TreeLevel objects - so I need to make sure they aren't setting that Boolean in any new models. I think I can override the save() method on TreeLevel to check the DB to see if another with that boolean as True exists - if so? Don't save the 'new' one and return an error. But I think this hits the database - causing unnecessary queries potentially?
Then additionally, I would also need to override the save() method on Layer and add the 'default' TreeLevel to that many2many at that time.
Is there a good way to 'automatically' handle this with these relationships?
My ultimate goal:
Have a default TreeLevel that is automatically added to every Layer many to many relationship whenever a Layer is created.
Questions:
Would my proposed solution work?
I've tried looking into Djangos Unique Constraints, but I think those are more on a row level, as opposed to a Table level.

Django: How to efficiently filter model property methods?

I've been looking for a way to efficiently filter Django model objects using the YourModel.objects.filter() method with property values.
Django is unable to filter through property methods in this way.
It's still possible with list comprehension:
streams = [object for object in YourModel.objects.all() if object.property_method == 'value']
I've been told that this method isn't efficient because you still iterate through each object in the SQL table instead of the apparently more efficient SQL filter way.
Really I'd like to know what the best way to do this would be.
Here's my example models:
# someapp/models.py
from django.db import models
class Platform(models.Model):
name = models.CharField(max_length=50, null=True)
def __str__(self):
return self.name
class Account(models.Model):
platform = models.ForeignKey(Platform, on_delete=models.PROTECT)
name = models.CharField(verbose_name="Account Name", max_length=100, null=True)
profile_pic = models.ImageField(upload_to='profile_pictures', null=True, blank=True)
def __str__(self):
return self.name
class Stream(models.Model):
host = models.ForeignKey(Account, on_delete=models.CASCADE, verbose_name="Host")
def __str__(self):
return self.host.name
#property
def platform(self):
return self.host.platform
I'm thinking that it might just be best to make platform an attribute, but that kind of takes away the automatically adding part of it without external scripts.
What's the most efficient way to filter Objects (in this case "Stream" objects) by a property method? or is it better to do this a different way?
You can't filter by a method since you can't translate that to SQL. That said, in the specific example you gave you can chain the filter statement.
This is what it would look like: Stream.objects.filter(host__platform__name='value')
For more context on how this works: https://books.agiliq.com/projects/django-orm-cookbook/en/latest/join.html
Property is defined in application level, if you would like to filter through property, you have to load the objects into Python to evaluate the property first then you can loop through. This is ideal for single object evaluation, while for multiple objects, you can always delegate the computation to db using Queryset annotation and aggregation
qs = Stream.objects.annotate(
# you can annotate what you would like to compute in your model, not necessary a field
platform = F('host__platform__name')
).filter(
platform = 'value'
)
Then, each Stream object will have .platform attribute available. You can simply looping through the annotated value using [i.platform for i in qs]
A general situation could be:
Model.objects.annotate(
&ltname> = &ltcomputed_value>
).filter(
&ltcondition>
)

Django customize ForeignKey way of instances returnment

I have a need to create custom field, that is very similar to a ForeignKey field but has specific logic. Two main tasks are:
Each related model of this CustomForeignKey has regular ForeignKey field to the model itself and my purpose is to return one of this instances depending on some parameter (date for example). Maybe it would be more clear with some example:
class Author(models.Model):
name = models.CharField(max_length = 30)
surname = models.CharField(max_length = 60)
publication = CustomForeignKey(Publication)
class Publication(models.Model):
title = models.CharField(max_length = 50)
text = models.TextField()
date = models.DateTimeField()
source = models.ForeigKey('self', related_name='references')
I want calls to SomeAuthor.publication be performed like SomePublication.references.order_by('date').first(). Real goal is more difficult, but the sense is nearly the same.
Second thing is about lookups. When I filter objects by this CustomForeignKey, I would like to have same logic as in previous point. So Author.objects.filter(publication__title = 'Some Title') should make filtering by the fist object ordered by date from references related manager.
I read the documentation about creating custom fields in django, but there are no good examples about custom relational fields. In django.db.models.fields.related as well, I didn't find which methods should I redefine to achieve my goal. There are to_python and from_db_value, but they are used only in regular fields, not related ones.
Maybe someone had more experince with custom relational fields, so I would be tankful for any advice!

How do you force only one relationship in django when multiple are possible?

I am creating a web application to manage robotics teams for our area. In the application I have a django model that looks like this:
class TeamFormNote(models.Model):
team = models.ForeignKey(Team, blank=True, null=True)
member = models.ForeignKey(TeamMember, blank=True, null=True)
notes = models.TextField()
def __unicode__(self):
if self.team:
return "Team Form Record: " + unicode(self.team)
if self.member:
return "Member Form Record: " + unicode(self.member)
Essentially, I want it to have a relationship with team or a relationship with member, but not both. Is there a way to enforce this?
I can only see two viable solutions. First is actually the same as #mariodev suggested in the comment which is to use Genetic foreign key. That will look something like:
# make sure to change the app name
ALLOWED_RELATIONSHIPS = models.Q(app_label = 'app_name', model = 'team') | models.Q(app_label = 'app_name', model = 'teammember')
class TeamFormNote(models.Model):
content_type = models.ForeignKey(ContentType, limit_choices_to=ALLOWED_RELATIONSHIPS)
relation_id = models.PositiveIntegerField()
relation = generic.GenericForeignKey('content_type', 'relation_id')
What that does is it sets up a generic foreign key which will allow you to link to any other model within your project. Since it can link to any other model, to restrict it to only the models you need, I use the limit_choices_to parameter of the ForeignKey. This will solve your problem since there is only one generic foreign key hence there is no way multiple relationships will be created. The disadvantage is that you cannot easily apply joins to generic foreign keys so you will not be able to do things like:
Team.objects.filter(teamformnote_set__notes__contains='foo')
The second approach is to leave the model as it and manually go into the database backend and add a db constaint. For example in postgres:
ALTER TABLE foo ADD CONSTRAINT bar CHECK ...;
This will work however it will not be transparent to your code.
This sounds like a malformed object model under the hood...
How about an abstract class which defines all common elements and two dreived classes, one for team and one for member?
If you are running into trouble with this because you want to have both referenced in the same table, you can use Generic Relations.

Putting in extra restrictions when filtering on foreignkey in django-admin

When getting members based on Unit, I only want to get the ones who are actually in that unit as of now.
I've got a model looking like this:
class Member(models.Model):
name = models.CharField(max_length=256)
unit = models.ManyToManyField(Unit, through='Membership')
class Membership(models.Model):
member = models.ForeignKey(Member)
unit = models.ForeignKey(Unit)
start = models.DateField(default=date.today)
stop = models.DateField(blank=True, null=True)
class Unit(models.Model):
name = models.CharField(max_length=256)
As you can see, members can have a "fake" membership in unit, that is only history and should not be considered in the searches and listings of the admin. They should be shown in the change-page for a single object though.
The admin looks like this:
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
class MemberAdmin(admin.ModelAdmin):
list_filter = ('unit',)
inlines = [MembershipInline,]
So how can I (if at all possible this way), when filtering on unit only get those units whose membership__stop__isnull=True?
I tried Managers, I can make them work on the model in the admin itself, but not on the filtering/searches. There is also a def queryset(self) method that is overrideable, but I can't wrap my head around how to use it to fix my problem.
Edit, how this is used: A member has only one membership in a unit, however, they could be members from before, but they are ended (with stop). So I only want to filter (and show, in the list view) those members who have an open-ended membership (like, that they are members of that unit now).
Any ideas?
So you're trying to get the members of a specific Unit, right?
unit = Unit.objects.select_related().get(id=some_id)
This will pull the unit out of the database for you, along with the Memberships and Users that belong to it. You can access and filter the users by:
for member in unit.membership__set.filter(stop__isnull=True):
print member.name
I hope this helps? I may be wrong, I haven't tested this.
One way to certainly achieve this is by adding a denormalized field for
has_open_ended_membership.
To do this just add a BooleaneField like that to the Member and make sure it's consistent.
From the django documentation this seems to be the only way without writing specialized code in the ModelAdmin object:
Set list_filter to activate filters in
the right sidebar of the change list
page of the admin. This should be a
list of field names, and each
specified field should be either a
BooleanField, CharField, DateField,
DateTimeField, IntegerField or
ForeignKey.
I'm curious about other approaches - list_filter certainly is limited.
I fixed it with putting in a denormalized field in member, with a foreign-key to the active unit. Then, to make it work and be automatically updated in the admin, I made the specialized save-function for Membership.
class Member(models.Model):
name = models.CharField(max_length=256)
unit = models.ManyToManyField(Unit, through='Membership')
unit_denorm = models.ForeignKey(Unit)
class Membership(models.Model):
member = models.ForeignKey(Member)
unit = models.ForeignKey(Unit)
start = models.DateField(default=date.today)
stop = models.DateField(blank=True, null=True)
def save(self, *args, **kwargs):
if not self.stop:
self.member.unit_denorm = self.unit
self.member.save()
super(Membership, self).save(*args, **kwargs)
class Unit(models.Model):
name = models.CharField(max_length=256)
And with list_filter = ('unit_denorm',) in the admin, it does exactly what I want.
Great! Of course, there should only be one field with stop__isnull=True. I haven't figured out how to make that restriction. but people using the system know they shouldn't do that anyway.

Categories