I am making a django project with models.py having following code:
class Record(models.Model):
id = models.AutoField(primary_key=True)
class TTAMRecord(Record):
client = models.ForeignKey(TTAMClient)
class TTAMClient(models.Model):
...
class Account(models.Model):
records = models.ManyToManyField(Record)
...
I also have following code to insert a TTAMRecord into the records of an Account:
account = Account.objects.get(...)
client = TTAMClient.objects.create(...)
record = TTAMRecord.objects.create(..., client = client, ...)
account.records.add(record)
What I want to do(but can't) is to call the client within the record object of an account; e.g.:
account = Account.objects.get(...)
for record in account.records.all():
client = record.client
...
However, if I am not allowed to do this, since record here is stored as a Record (which doesn't have client) type instead of TTAMRecord(which has client) type...
Any idea how to cast the object?
I want to use the more generic Record instead of TTAMRecord for some purposes not stated here...
As Record is not abstract model, it has its own table and life cycle as other models. However, you can access the corresponding client object as record.ttamclient, so you can change your line to
account = Account.objects.get(...)
for record in account.records.all():
client = record.ttamclient
...
However, this is little cumbersome if you have multiple derived classes. In such cases you would have to know which derived class you are referring to and use the corresponding attribute name.
If I understood your concept of "cast" it will not work in the way you described.
However, to get model inheritance to work you need to use abstract models (see docs)
class Record(models.Model):
# Some generic fields
class Meta:
abstract = True
class TTAMRecord(Record):
client = models.ForeignKey(TTAMClient)
If you need both Record and TTAMRecord to be stored in Account you will need to use polymorphic relationships, that in Django it's called Generic Relations (see docs)
You will need an intermediary model to store these generic relations. So, basically you will have a AccountRecord and a Account model:
class AccountRecord(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Account(models.Model):
records = models.ManyToManyField(AccountRecord)
And so you can do:
account = Account.objects.get(...)
for record in account.records.all():
record_content = record.content_object
if isinstance(record_content, TTAMRecord):
client = record_content.client
else:
# No client available
Related
I've been building a email/sms notification engine. A person or group can be subscribed to an object and if the object gets updated, the people/groups will be notified of the change by email/sms.
Currently, I've implemented it as below:
models.py
class Subscription(models.Model):
# subscribers
people = models.ManyToManyField(Person)
groups = models.ManyToManyField(Group)
# mandatory fields for generic relation
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
mixins.py
class NotificationMixin(object):
def perform_update(self, serializer):
model_name = str.lower(serializer.Meta.model)
old_obj = model.objects.get(id=serializer.data['id'])
obj = serializer.save()
self.notify(model_name, old_obj, obj)
def notify(self, model_name, old_obj, obj):
# All models have a GenericRelation field for reverse searching
subscriptions = Subscription.objects.filter(**{ model_name: obj })
// *rest of logic to iterate over subscriptions and email people/groups
Using django's ContentType generic relations, I can subscribe a person/group to any object.
I want to add the capability to create global subscriptions using the same Subscription model so that they are all stored in the same table. A global subscription will not have an object that it is tracking, but when any object of a specific model is triggered, emails will be sent.
I'm having trouble generalizing my subscription model to be able to accept a model instance or the model for triggering a response.
The functionality I want:
Global Subscriptions
People/Groups updated by if any object of the model X is changed
Object Level Subscriptions
People/Groups updated if specific object is updated
Is the current model/architecture that I have a good way to go about this problem, or should I approach this differently?
Note The frontend is in AngularJs, so this is exclusively interacting with our django api.
For anybody who may want to find a solution to this, I ended up doing:
class Subscription(models.Model):
"""
Model for subscribing to object changes.
Can be subscribed to any object or any model type.
Subcriptions:
model - if any object changes of this type that belongs to the company, update
object - if that specific object changes, update
To create:
Either give a content_object or a content_type. Content object is a model instance. Content type is a ContentType (i.e. Study, Product, etc.)
If it is created wrong, will just be lost in database forever (unless you know it's there, then delete it.)
"""
people = models.ManyToManyField(Person)
groups = models.ManyToManyField(Group)
trigger = models.CharField(max_length=50)
APP_LABELS = [apps to limit the available choices]
# for object subscription
# mandatory fields for generic relation
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
def save(self, *args, **kwargs):
'''
Save logic to validate the Subscription model. This is run after the native create() method.
'''
# check if no content_object, then content_type must be defined by user
# probably not necessary since it will fail at creation if content_type isn't an instance of a ContentType, but good to double check
if not self.content_object:
if self.content_type.__class__ != ContentType:
if type(self.content_type) == str:
# if content_type is a string designating a model
self.content_type = ContentType.objects.get(model=self.content_type)
else:
# if content_type is a model class
self.content_type = ContentType.objects.get_for_model(Study)
apps = ', '.join(map(str, self.APP_LABELS))
# check if content_type in our defined apps
if self.content_type.app_label not in apps:
raise ValidationError('Please select a content_object or content_type in apps: {0}'.format(apps))
super(Subscription, self).save(*args, **kwargs)
I'm building a simple API for an ESP8266 to connect to in an IoT application, passing a JSON string. In this application there are multiple Monitors (internet connected devices) per Site (location/address), and multiple LogEntries per Site/Monitor.
The API was originally setup with an endpoint like:
/api/logentries/
Posting a JSON string like:
{"site":"abcd","monitor":"xyz","data_point":"value"}
In the object model, Monitor is a child of Site, but for convenience of entry creation and reporting, the JSON format of the LogEntry posted by each device flattens this structure out, meaning that the LogEntry model also has a FK relationship for both Site and Monitor. In the code below, "textID" is the ID used within the context of the API for the Site/Monitor (e.g. PK values remain "hidden" for API callers).
In models.py:
class Site(models.Model):
name = models.CharField(max_length=32)
textID = models.CharField(max_length=32, blank=True, db_index=True, unique=True)
class Monitor(models.Model):
textID = models.CharField(max_length=32)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
class Meta:
unique_together = ('site', 'textID')
class LogEntry(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
monitor = models.ForeignKey(Monitor, on_delete=models.CASCADE)
data_point = models.CharField(max_length=8, default='')
To get this to work on a single site, I created a custom serializer:
class LogEntrySerializer(serializers.HyperlinkedModelSerializer):
site = serializers.SlugRelatedField(slug_field='textID', queryset=Site.objects.all())
monitor = serializers.SlugRelatedField(slug_field='textID', queryset=Monitor.objects.filter())
class Meta:
model = LogEntry
fields = ('pk', 'site', 'monitor', 'data_point', )
This works for reading valid data, and saving when all monitor IDs are unique across sites.
However, if two sites have a Monitor with the same textID—e.g. "Site1/001" and "Site2/001" this breaks, as the Monitor.objects.all() results in multiple records being retrieved (which makes sense and is expected behaviour).
What I'm wanting to do is to have the second queryset (for monitor) limited to the specified site, to avoid this error.
This post almost answers my question, however it benefits from the second field value (user) being available in the request object, something that is not available in this case.
Is there a way I can retrieve the Site.pk or Site.textID for the queryset value to resolve correctly--e.g. queryset=Monitor.objects.filter(site__textID=xxx)--what would 'xxx' be? Or do I need to completely override the serializer (and not rely on SlugRelatedField)? Or some other approach that might work?
(As an aside: I recognise that this could be achieved by modifying the URL pattern to something like /api///logentries, which would then have this information available as part of the request/context and from a normalisation perspective would be better also. However this would require reflashing of a number of already deployed devices to reflect the changed API details, so I'd like to avoid such a change if possible, even though upon reflection this is probably a cleaner solution/approach long-term.)
Thanks in advance.
You'll need to write your own SlugRelatedField subclass. The unicity constraint that applies to a SlugRelatedField doesn't apply to your case.
This can be done by creating a subfield and overriding the get_value to retrieve the site/monitor tuple and to_internal_value to select the appropriate monitor.
Thanks to the pointers from Linovia, the following field class resolves the issue:
class MonitorRelatedField(serializers.Field):
def to_representation(self, obj):
return obj.textID
def get_value(self, data):
site_textID = data['site']
monitor_textID = data['monitor']
return ( site_textID, monitor_textID, )
def to_internal_value(self, data):
return Monitor.objects.get(site__textID=data[0], textID=data[1])
I have a Django (1.8) Model for an underlying database table that has multiple columns that are logically a fixed-size array. For example:
from django.db import models
class Widget(models.Model):
# ...
description_1 = models.CharField(max_length=255)
description_2 = models.CharField(max_length=255)
description_3 = models.CharField(max_length=255)
# ...
I would like to be able to access these columns as if they were a collection on the model instance, e.g.:
instance = Widget.objects.get(...)
for description in instance.descriptions:
# do something with each description
My primary motivation is that I am exposing this model via Django Rest Framework (DRF), and would like the API clients to be able to easily enumerate the descriptions associated with the model. As it stands, the clients have to reference each logical 'index' manually, which makes the code repetitive.
My DRF serializer code is currently like this:
class WidgetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Widget
There are a fixed number of descriptions for each Widget, and their ordering is important.
Is there a clean way to expose these fields as a collection on the Model object?
It really was as easy as adding a method to the Model class that returns the fields as a sequence, and then (for API clients), manually specifying that new method as a field to serialize.
So the Model definition becomes:
from django.db import models
class Widget(models.Model):
description_1 = models.CharField(max_length=255)
description_2 = models.CharField(max_length=255)
description_3 = models.CharField(max_length=255)
def descriptions(self):
return self.description_1, self.description_2, self.description_3
And the DRF serializer is updated like:
class WidgetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Widget
fields = ('url', 'descriptions',)
This causes the API to return a JSON array for descriptions and omit all of the individual description_x fields.
I am using Django Rest Framework, and want to allow API clients to create resources, where one attribute of the created resource is the (required) primary key of a related data structure. For example, given these models:
class Breed(models.Model):
breed_name = models.CharField(max_length=255)
class Dog(models.Model):
name = models.CharField(max_length=255)
breed = models.ForeignKey(Breed)
I want to allow the caller to create a Dog object by specifying a name and a breed_id corresponding to the primary key of an existing Breed.
I'd like to use HyperlinkedModelSerializer in general for my APIs. This complicates things slightly because it (apparently) expects related fields to be specified by URL rather than primary key.
I've come up with the following solution, using PrimaryKeyRelatedField, that behaves as I'd like:
class BreedSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Breed
class DogSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Dog
read_only_fields = ('breed', )
breed_id = serializers.PrimaryKeyRelatedField(queryset=Breed.objects.all())
def create(self, validated_data):
validated_data['breed'] = validated_data['breed_id']
del validated_data['breed_id']
return Dog.objects.create(**validated_data)
But it seems weird that I would need to do this mucking around with the overloaded create. Is there a cleaner solution to this?
Thanks to dukebody for suggesting implementing a custom related field to allow an attribute to be serialized OUT as a hyperlink, but IN as a primary key:
class HybridPrimaryKeyRelatedField(serializers.HyperlinkedRelatedField):
"""Serializes out as hyperlink, in as primary key"""
def to_internal_value(self, data):
return self.get_queryset().get(pk=data)
This lets me do away with the create override, the read_only_fields decorator, and the weirdness of swapping out the breed and breed_id:
class BreedSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Breed
class DogSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Dog
breed = HybridPrimaryKeyRelatedField(queryset=Breed.objects,
view_name='breed-detail')
I have a django auth user proxy model that has some extra permissions attached to it like so:
class User(User):
class Meta:
proxy = True
permissions = (
("write_messages","May add new messages to front page"),
("view_maps","Can view the maps section"),
)
Elsewhere in the model I have an object that has User as a foreign key:
class Project(models.Model):
creation_date = models.DateTimeField(auto_now_add=True)
modify_date = models.DateTimeField(auto_now=True)
owner = models.ForeignKey(User)
In one of my views I attempt to create a new project using the current user as the owner
#login_required
def new_project(request):
project = Project.objects.create(owner=request.user)
This, however, returns the error:
Cannot assign "<User: mwetter>": "Project.owner" must be a "User" instance.
I'm assuming that the foreign key owner in the project object is referring to the base class for Django User instead of my proxy class. Is there a way to refer to my proxy user as a foreign key instead?
In addition to what #DavidRobinson said, you need to be careful about creating foreign keys to proxy models. A proxy model is still a subclass of the model it proxies, despite being for all intents and purposes the same as the model. If you have a foreign key to the proxy it will not accept the base class, however a foreign key to the base class will accept the the proxy. Take the following for example.
Given:
class UserProxy(User):
class Meta:
proxy = True
...
class User(models.Model):
...
The following will raise an exception:
class SomeModel(models.Model):
user = models.ForeignKey(UserProxy)
user = User.objects.get(username='some_user')
instance = SomeModel.objects.create(user=user)
But, the following will work fine:
class SomeModel(models.Model):
user = models.ForeignKey(User)
user = UserProxy.objects.get(username='some_user')
instance = SomeModel.objects.create(user=user)
This is due to the directionality of the relationship. UserProxy is-a User, but User is not a UserProxy.
Why are you calling your proxy class User, which is guaranteed to lead to confusion between that and django.contrib.auth.models.User? Why not UserProxy, or MyUser? Then it could be distinguished whenever you use it.
Try to assign id instead of object
project = Project.objects.create(owner_id=request.user.pk)