Expose multiple similar database fields as enumerable collection - python

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.

Related

Django - save copy of instance in inherited model

Using Django 2.1.5, I have one model that is completely inherited by another model. Both tables in DB. I want to save 'revisions' of the model as the inherited model. All fields should be the same at the time of the copy (including the id/pk).
What's the right and quick way to copy the instance of the parent model to the inherited?
Let's say these are the models (but with a lot of fields, foreign keys, json fields..):
class MyModel(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True, unique=True, primary_key=True)
identifier = models.IntegerField(default=-1)
title = models.CharField(max_length=1000)
revision = models.IntegerField(default=0)
class MyModelRevisions(MyModel):
pass
Now, I want to take an instance of MyModel and completely copy it to MyModelRevisions.
I thought of something like this:
model_revision = MyModelRevisions(MyModel.objects.get(pk=my_model.pk))
model_revision.save()
But I'm getting an error message saying that the title of my_model is not a valid UUID.
A simple answer would be something like serialize/deserialize the object to create a new one:
from django.forms.models import model_to_dict
revision = MyModelRevisions(**model_to_dict(my_model_instance, fields=['id', 'identifier', 'title', 'revision']))
revision.save()
But maybe you want to give a look to something like django-reversion

django rest framework - global default model serializer

In short, I want to have a global default serializer per model. My use case here is to create dynamic serializer- i.e creating ModelSerializer classes on the fly.
class Customer(models.Model):
name = models.CharField(max_length=200)
code = models.CharField(max_length=200)
# many more fields..
class CustomerTicket(models.Model):
customer = models.ForeignKey(Customer)
date = models.DateTimeField(auto_now_add=True)
# more fields..
Customer will be referenced by many other models, and hence it will be serialized as a nested object. I don't want the 'code' field to appear in the output - no matter what it should always be excluded.
Now I'd like to create a function:
def serialize_default(model, fields, queryset):
class S(serializers.ModelSerializer):
class Meta:
model = model
fields = fields
depth = 1
return S(queryset, many=True)
if I serialize CustomerTicket queryset using this function, I will get all the customer fields as a nested object. I know I can override it locally, but I want to define a CustomerSerializer that will be used by default (for the nested Customer here) unless other serializer is specified as a field. How to achieve this?
Would something like that work for you?
class DefaultCustomerSerializer(serializers.ModelSerializer):
# whatever fields you want
class DefaultCustomerSerializerModel(serializers.ModelSerializer):
customer = DefaultCustomerSerializer()
# You can inherit from this to have default customer serializer
# on serializers you want.
class CustomerTicketSerializer(DefaultCustomerSerializerModel):
# Other fields

Create resource via POST specifying related field ID

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')

Multiple Django Admin Arguments with Extensions

Is there a way to use multiple Django extensions in the admin.site.register() inside admin.py? I'm using "simple-history" and "import-export" extensions, but I can only have one of them in the admin.site.register().
Example: I have a model named, "Cars", that is using the "simple-history" extension so I need admin.site.register(Cars, SimpleHistoryAdmin), as their documentation says it should. I want to use the import/export extension as well to the same "Cars" model, but the admin.site.register() doesn't take multiple arguments for me to add it.
models.py
class Cars(models.Model):
Year = models.CharField(max_length=30)
Make = models.CharField(max_length=30)
Model = models.CharField(max_length=30)
history = HistoricalRecords()
class Meta:
verbose_name_plural = "Car Table"
def __str__(self):
return self.Make
admin.py
class CarResource(resources.ModelResource):
class Meta:
model = Cars
fields = ('id','Year', 'Make', 'Model',)
class CarAdmin(ImportExportModelAdmin):
resource_class = CarResource
pass
#I want to use the import/export extension (code above), along with simple-history
admin.site.register(Cars, CarAdmin)
admin.site.register(Cars, SimpleHistoryAdmin)
I've tried using a proxy and inlines, but the proxy makes a new model which I don't want and when using inlines I get an error saying that it requires a foreign key, but I'm not trying to get the model objects from a different model. Naming them the same model doesn't work because the model is already registered. Any help is much appreciated!
In python, class can have more than one parent. Just inherit from 2 parents at once. But both ImportExportModelAdmin and SimpleHistoryAdmin are inheriting from ModelAdmin, that's not good. There is also ImportExportMixin, we can use it instead of ImportExportModelAdmin, so there will be only one reference to ModelAdmin.
class CarResource(resources.ModelResource):
class Meta:
model = Cars
fields = ('id','Year', 'Make', 'Model',)
class CarAdmin(ImportExportMixin, SimpleHistoryAdmin):
resource_class = CarResource
pass
#I want to use the import/export extension (code above), along with simple-history
admin.site.register(Cars, CarAdmin)

django abstract models versus regular inheritance

Besides the syntax, what's the difference between using a django abstract model and using plain Python inheritance with django models? Pros and cons?
UPDATE: I think my question was misunderstood and I received responses for the difference between an abstract model and a class that inherits from django.db.models.Model. I actually want to know the difference between a model class that inherits from a django abstract class (Meta: abstract = True) and a plain Python class that inherits from say, 'object' (and not models.Model).
Here is an example:
class User(object):
first_name = models.CharField(..
def get_username(self):
return self.username
class User(models.Model):
first_name = models.CharField(...
def get_username(self):
return self.username
class Meta:
abstract = True
class Employee(User):
title = models.CharField(...
I actually want to know the difference between a model class that
inherits from a django abstract class (Meta: abstract = True) and a
plain Python class that inherits from say, 'object' (and not
models.Model).
Django will only generate tables for subclasses of models.Model, so the former...
class User(models.Model):
first_name = models.CharField(max_length=255)
def get_username(self):
return self.username
class Meta:
abstract = True
class Employee(User):
title = models.CharField(max_length=255)
...will cause a single table to be generated, along the lines of...
CREATE TABLE myapp_employee
(
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
...whereas the latter...
class User(object):
first_name = models.CharField(max_length=255)
def get_username(self):
return self.username
class Employee(User):
title = models.CharField(max_length=255)
...won't cause any tables to be generated.
You could use multiple inheritance to do something like this...
class User(object):
first_name = models.CharField(max_length=255)
def get_username(self):
return self.username
class Employee(User, models.Model):
title = models.CharField(max_length=255)
...which would create a table, but it will ignore the fields defined in the User class, so you'll end up with a table like this...
CREATE TABLE myapp_employee
(
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
An abstract model creates a table with the entire set of columns for each subchild, whereas using "plain" Python inheritance creates a set of linked tables (aka "multi-table inheritance"). Consider the case in which you have two models:
class Vehicle(models.Model):
num_wheels = models.PositiveIntegerField()
class Car(Vehicle):
make = models.CharField(…)
year = models.PositiveIntegerField()
If Vehicle is an abstract model, you'll have a single table:
app_car:
| id | num_wheels | make | year
However, if you use plain Python inheritance, you'll have two tables:
app_vehicle:
| id | num_wheels
app_car:
| id | vehicle_id | make | model
Where vehicle_id is a link to a row in app_vehicle that would also have the number of wheels for the car.
Now, Django will put this together nicely in object form so you can access num_wheels as an attribute on Car, but the underlying representation in the database will be different.
Update
To address your updated question, the difference between inheriting from a Django abstract class and inheriting from Python's object is that the former is treated as a database object (so tables for it are synced to the database) and it has the behavior of a Model. Inheriting from a plain Python object gives the class (and its subclasses) none of those qualities.
The main difference is how the databases tables for the models are created.
If you use inheritance without abstract = True Django will create a separate table for both the parent and the child model which hold the fields defined in each model.
If you use abstract = True for the base class Django will only create a table for the classes that inherit from the base class - no matter if the fields are defined in the base class or the inheriting class.
Pros and cons depend on the architecture of your application.
Given the following example models:
class Publishable(models.Model):
title = models.CharField(...)
date = models.DateField(....)
class Meta:
# abstract = True
class BlogEntry(Publishable):
text = models.TextField()
class Image(Publishable):
image = models.ImageField(...)
If the Publishable class is not abstract Django will create a table for publishables with the columns title and date and separate tables for BlogEntry and Image. The advantage of this solution would be that you are able to query across all publishables for fields defined in the base model, no matter if they are blog entries or images. But therefore Django will have to do joins if you e.g. do queries for images...
If making Publishable abstract = True Django will not create a table for Publishable, but only for blog entries and images, containing all fields (also the inherited ones). This would be handy because no joins would be needed to an operation such as get.
Also see Django's documentation on model inheritance.
Just wanted to add something which I haven't seen in other answers.
Unlike with python classes, field name hiding is not permited with model inheritance.
For example, I have experimented issues with an use case as follows:
I had a model inheriting from django's auth PermissionMixin:
class PermissionsMixin(models.Model):
"""
A mixin class that adds the fields and methods necessary to support
Django's Group and Permission model using the ModelBackend.
"""
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
class Meta:
abstract = True
# ...
Then I had my mixin which among other things I wanted it to override the related_name of the groups field. So it was more or less like this:
class WithManagedGroupMixin(object):
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
related_name="%(app_label)s_%(class)s",
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
I was using this 2 mixins as follows:
class Member(PermissionMixin, WithManagedGroupMixin):
pass
So yeah, I expected this to work but it didn't.
But the issue was more serious because the error I was getting wasn't pointing to the models at all, I had no idea of what was going wrong.
While trying to solve this I randomly decided to change my mixin and convert it to an abstract model mixin. The error changed to this:
django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'
As you can see, this error does explain what is going on.
This was a huge difference, in my opinion :)
The main difference is when you inherit the User class. One version will behave like a simple class, and the other will behave like a Django modeel.
If you inherit the base "object" version, your Employee class will just be a standard class, and first_name won't become part of a database table. You can't create a form or use any other Django features with it.
If you inherit the models.Model version, your Employee class will have all the methods of a Django Model, and it will inherit the first_name field as a database field that can be used in a form.
According to the documentation, an Abstract Model "provides a way to factor out common information at the Python level, whilst still only creating one database table per child model at the database level."
I will prefer the abstract class in most of the cases because it does not create a separate table and the ORM does not need to create joins in the database. And using abstract class is pretty simple in Django
class Vehicle(models.Model):
title = models.CharField(...)
Name = models.CharField(....)
class Meta:
abstract = True
class Car(Vehicle):
color = models.CharField()
class Bike(Vehicle):
feul_average = models.IntegerField(...)

Categories

Resources