Parametrize a Python class - python

Is there any way to parametrize a class in Python? The parametrized class may look something like this:
class FIELDSerializer:
FIELD = serializers.CharField(source='get_FIELD_display', required=False)
class Meta:
model = None
fields = {FIELD}
Which would need to create the following three classes:
class NameSerializer:
name = serializers.CharField(source='get_name_display', required=False)
class Meta:
model = None
fields = {'name'}
class CategorySerializer:
category = serializers.CharField(source='get_category_display', required=False)
class Meta:
model = None
fields = {'category'}
class StateSerializer:
state = serializers.CharField(source='get_state_display', required=False)
class Meta:
model = None
fields = {'state'}
Is this possible or not?

You can do what you want with a factory function, although it's not completely trivial to get the internal variables (attributes) as you want them:
def factory(FIELDname):
class FIELDSerializer:
class Meta:
model = None
fields = {FIELDname}
settatr(FIELDSerializer, FIELDname, serializers.CharField(source=f'get_{FIELDname}_display', required=False))
return FIELDSerializer
CategorySerializer = factory('category')
StateSerializer = factory('state')
NameSerializer = factory('name')
The setattr allows us to set the name of attribute to the FIELDname string. (Thanks to #Code-Apprentice and #juanpa.arrivillaga for this idea.)
I don't know if there's any easy way to avoid the repetition of the field name and the desired class name when you call the factory without using something like exec (which is perfectly legal but usually leaves programmers with a bad taste in their mouths).

Related

Django model unique together both ways

Many questions already on this topic, but not what i'm searching for.
I have this Model:
class Options(TimeStampedModel)
option_1 = models.CharField(max_length=64)
option_2 = models.CharField(max_length=64)
class Meta:
unique_together = ('option_1', 'option_2')
Now I have a unique constraint on the fields.
Is there a way to also define this the other way around so that it doesn't matter what was option_1 and what was option_2
As example:
Options.create('spam', 'eggs') # Allowed
Options.create('spam', 'eggs') # Not allowed
Options.create('eggs', 'spam') # Is allowed but should not be
Thanks in advance!
I think a ManyToMany relation with a custom through table and an unique_together constraint on that table should do what you want.
Example code:
from django.db.models import Model, ForeignKey, ManyToManyField, CharField
class Option(Model):
name = CharField()
class Thing(TimeStampedModel):
options = ManyToManyField("Option", through="ThingOption")
class ThingOption(Model):
thing = ForeignKey(Thing)
option = ForeignKey(Option)
value = CharField()
class Meta:
unique_together = ('thing', 'option')
For Django 2.2+ it is recommended to use UniqueConstraint. In the docs there is a note stating unique_together may be deprecated in the future. See this post for its usage.
You can override create method, do something like
from django.db import models
class MyModelManager(models.Manager):
def create(self, *obj_data):
# Do some extra stuff here on the submitted data before saving...
# Ex- If obj_data[0]=="eggs" and obj_data[1]=="spam" is True don't allow it for your blah reason
# Call the super method which does the actual creation
return super().create(*obj_data) # Python 3 syntax!!
class MyModel(models.model):
option_1 = models.CharField(max_length=64)
option_2 = models.CharField(max_length=64)
objects = MyModelManager()

django rest framework - how to add a static field value for a serializer

I need to add a static field to my serializer. It should always return the same value, regardless of the passed object. Currently I implemented it like so:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
#staticmethod
def get_type(obj):
return 'question'
class Meta:
model = Question
fields = ('type',)
But is there a simpler way to do it, without the SerializerMethodField?
using a ReadOnlyField worked for me:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.ReadOnlyField(default='question')
class Meta:
model = Question
fields = ('type',)
https://www.django-rest-framework.org/api-guide/fields/#readonlyfield
The only alternative would be to override to_representation and add the value there:
def to_representation(self, obj):
data = super().to_representation(obj)
data['type'] = 'question'
return data
Not a much better option though.
You can use serializers.HiddenField
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.HiddenField(default='question')
class Meta:
model = Question
fields = ('type',)
http://www.django-rest-framework.org/api-guide/fields/#hiddenfield
If you don't care about adding a line to your model, it would be easier to add static field to your model.
class Question(models.Model):
type = 'question'
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ('type',)

django rest framework abstract class serializer

I have some models like these:
class TypeBase(models.Model):
name = models.CharField(max_length=20)
class Meta:
abstract=True
class PersonType(TypeBase):
pass
class CompanyType(TypeBase):
pass
Having this, I want to create just one serializer that holds all these field types (serialization, deserialization, update and save).
To be more specific, I want only one serializer (TypeBaseSerializer) that print the Dropdown on the UI, serialize the json response, deserialize it on post and save it for all my based types.
Something like this:
class TypeBaseSerializer(serializers.Serializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
Is it possible?
I think the following approach is more cleaner. You can set "abstract" field to true for the base serializer and add your common logic for all child serializers.
class TypeBaseSerializer(serializers.ModelSerializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
abstract = True
def func(...):
# ... some logic
And then create child serializers and use them for data manipulation.
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = ('id', 'name')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = ('id', 'name')
Now you can use the both of these serializers normally for every model.
But if you really want to have one serializers for both the models, then create a container model and a serializer for him too. That is much cleaner :)
You can't use a ModelSerializer with an abstract base model.
From restframework.serializers:
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
I wrote a serializer_factory function for a similar problem:
from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.
:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""
def _get_declared_fields(attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
return OrderedDict(fields)
# Create an object that will look like a base serializer
class Base(object):
pass
Base._declared_fields = _get_declared_fields(kwargss)
class MySerializer(Base, ModelSerializer):
class Meta:
model = mdl
if fields:
setattr(Meta, "fields", fields)
return MySerializer
You can then use the factory to produce serializers as needed:
def typebase_serializer_factory(mdl):
myserializer = serializer_factory(
mdl,fields=["id","name"],
#owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses
)
return myserializer
Now instanciate different subclass serializers:
persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)
As already mentioned in Sebastian Wozny's answer, you can't use a ModelSerializer with an abstract base model.
Also, there is nothing such as an abstract Serializer, as some other answers have suggested. So setting abstract = True on the Meta class of a serializer will not work.
However you need not use use a ModelSerializer as your base/parent serializer. You can use a Serializer and then take advantage of Django's multiple inheritance. Here is how it works:
class TypeBaseSerializer(serializers.Serializer):
# Need to re-declare fields since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
class Meta:
fields = ['id', 'name']
def someFunction(self):
#... will be available on child classes ...
pass
class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ['another_field']
class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields + ['some_other_field']
So now since the fields name and id are declared on the parent class (TypeBaseSerializer), they will be available on PersonTypeSerializer and since this is a child class of ModelSerializer those fields will be populated from the model instance.
You can also use SerializerMethodField on the TypeBaseSerializer, even though it is not a ModelSerializer.
class TypeBaseSerializer(serializers.Serializer):
# you will have to re-declare fields here since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
other_field = serializers.SerializerMethodField()
class Meta:
fields = ['id', 'name', 'other_field']
def get_other_field(self, instance):
# will be available on child classes, which are children of ModelSerializers
return instance.other_field
Just iterating a bit over #adki's answer:
it is possible to skip model for TypeBaseSerializer;
derived serializers can refer to TypeBaseSerializer.Meta, so you would change them in a single place.
class TypeBaseSerializer(serializers.Serializer):
class Meta:
fields = ('id', 'name', 'created')
abstract = True
def func(...):
# ... some logic
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields

Inherit and modify a `Meta` class

So I have a base ItemTable, and then a number of Tables that inherit from it. I don't seem to be able to modify the Meta class. I tried just including the meta class normally and it didn't work, then I found this bug report and implemented it below. It fails silently: the tables render only with the columns from the parent meta class.
class ItemTable(tables.Table):
class Meta:
model = Item
attrs = {"class":"paleblue"}
fields = ('name', 'primary_tech', 'primary_biz', 'backup_tech', 'backup_biz')
class ApplicationTable(ItemTable):
def __init__(self, *args, **kwargs):
super(ApplicationTable, self).__init__(*args, **kwargs)
class Meta(ItemTable.Meta):
model = Application
fields += ('jira_bucket_name',)
EDIT: Code amended as shown. I now get a NameError that fields is not defined.
Try:
class ApplicationTable(ItemTable):
class Meta:
model = Application
fields = ItemTable.Meta.fields + ('jira_bucket_name',)
You'll have the same problems extending Meta in a table, as you will in a normal Django model.
You didnt add , (comma) to one-element tuple. Try to change this line Meta.attrs['fields'] += ('jira_bucket_name') in ApplicationTable to:
Meta.attrs['fields'] += ('jira_bucket_name',)
if it didnt help try to create Meta class outsite model class definition:
class ItemTableMeta:
model = Item
attrs = {"class":"paleblue"}
fields = ('name', 'primary_tech', 'primary_biz', 'backup_tech', 'backup_biz')
class ApplicationTableMeta(ItemTableMeta):
model = Application
fields = ItemTableMeta.fields + ('jira_bucket_name',)
class ItemTable(tables.Table):
#...
Meta = ItemTableMeta
class ApplicationTable(ItemTable):
#...
Meta = ApplicationTableMeta
You may need to take this up with the django-tables author. This is not a problem with standard Django.

How does Django Know the Order to Render Form Fields?

If I have a Django form such as:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
And I call the as_table() method of an instance of this form, Django will render the fields as the same order as specified above.
My question is how does Django know the order that class variables where defined?
(Also how do I override this order, for example when I want to add a field from the classe's init method?)
New to Django 1.9 is Form.field_order and Form.order_fields().
# forms.Form example
class SignupForm(forms.Form):
password = ...
email = ...
username = ...
field_order = ['username', 'email', 'password']
# forms.ModelForm example
class UserAccount(forms.ModelForm):
custom_field = models.CharField(max_length=254)
def Meta:
model = User
fields = ('username', 'email')
field_order = ['username', 'custom_field', 'password']
[NOTE: this answer is now pretty completely outdated - please see the discussion below it, and more recent answers].
If f is a form, its fields are f.fields, which is a django.utils.datastructures.SortedDict (it presents the items in the order they are added). After form construction f.fields has a keyOrder attribute, which is a list containing the field names in the order they should be presented. You can set this to the correct ordering (though you need to exercise care to ensure you don't omit items or add extras).
Here's an example I just created in my current project:
class PrivEdit(ModelForm):
def __init__(self, *args, **kw):
super(ModelForm, self).__init__(*args, **kw)
self.fields.keyOrder = [
'super_user',
'all_districts',
'multi_district',
'all_schools',
'manage_users',
'direct_login',
'student_detail',
'license']
class Meta:
model = Privilege
I went ahead and answered my own question. Here's the answer for future reference:
In Django form.py does some dark magic using the __new__ method to load your class variables ultimately into self.fields in the order defined in the class. self.fields is a Django SortedDict instance (defined in datastructures.py).
So to override this, say in my example you wanted sender to come first but needed to add it in an init method, you would do:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
def __init__(self,*args,**kwargs):
forms.Form.__init__(self,*args,**kwargs)
#first argument, index is the position of the field you want it to come before
self.fields.insert(0,'sender',forms.EmailField(initial=str(time.time())))
Fields are listed in the order they are defined in ModelClass._meta.fields. But if you want to change order in Form, you can do by using keyOrder function.
For example :
class ContestForm(ModelForm):
class Meta:
model = Contest
exclude=('create_date', 'company')
def __init__(self, *args, **kwargs):
super(ContestForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = [
'name',
'description',
'image',
'video_link',
'category']
With Django >= 1.7 your must modify ContactForm.base_fields as below:
from collections import OrderedDict
...
class ContactForm(forms.Form):
...
ContactForm.base_fields = OrderedDict(
(k, ContactForm.base_fields[k])
for k in ['your', 'field', 'in', 'order']
)
This trick is used in Django Admin PasswordChangeForm: Source on Github
Form fields have an attribute for creation order, called creation_counter. .fields attribute is a dictionary, so simple adding to dictionary and changing creation_counter attributes in all fields to reflect new ordering should suffice (never tried this, though).
Use a counter in the Field class. Sort by that counter:
import operator
import itertools
class Field(object):
_counter = itertools.count()
def __init__(self):
self.count = Field._counter.next()
self.name = ''
def __repr__(self):
return "Field(%r)" % self.name
class MyForm(object):
b = Field()
a = Field()
c = Field()
def __init__(self):
self.fields = []
for field_name in dir(self):
field = getattr(self, field_name)
if isinstance(field, Field):
field.name = field_name
self.fields.append(field)
self.fields.sort(key=operator.attrgetter('count'))
m = MyForm()
print m.fields # in defined order
Output:
[Field('b'), Field('a'), Field('c')]
If either fields = '__all__':
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = '__all__'
or exclude are used:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
Then Django references the order of fields as defined in the model. This just caught me out, so I thought I'd mention it. It's referenced in the ModelForm docs:
If either of these are used, the order the fields appear in the form will be the order the fields are defined in the model, with ManyToManyField instances appearing last.
As of Django 1.7 forms use OrderedDict which does not support the append operator. So you have to rebuild the dictionary from scratch...
class ChecklistForm(forms.ModelForm):
class Meta:
model = Checklist
fields = ['name', 'email', 'website']
def __init__(self, guide, *args, **kwargs):
self.guide = guide
super(ChecklistForm, self).__init__(*args, **kwargs)
new_fields = OrderedDict()
for tier, tasks in guide.tiers().items():
questions = [(t['task'], t['question']) for t in tasks if 'question' in t]
new_fields[tier.lower()] = forms.MultipleChoiceField(
label=tier,
widget=forms.CheckboxSelectMultiple(),
choices=questions,
help_text='desired set of site features'
)
new_fields['name'] = self.fields['name']
new_fields['email'] = self.fields['email']
new_fields['website'] = self.fields['website']
self.fields = new_fields
For future reference: things have changed a bit since newforms. This is one way of reordering fields from base formclasses you have no control over:
def move_field_before(form, field, before_field):
content = form.base_fields[field]
del(form.base_fields[field])
insert_at = list(form.base_fields).index(before_field)
form.base_fields.insert(insert_at, field, content)
return form
Also, there's a little bit of documentation about the SortedDict that base_fields uses here: http://code.djangoproject.com/wiki/SortedDict
The easiest way to order fields in django 1.9 forms is to use field_order in your form Form.field_order
Here is a small example
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
field_order = ['sender','message','subject']
This will show everything in the order you specified in field_order dict.
Using fields in inner Meta class is what worked for me on Django==1.6.5:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Example form declaration with custom field order.
"""
from django import forms
from app.models import AppModel
class ExampleModelForm(forms.ModelForm):
"""
An example model form for ``AppModel``.
"""
field1 = forms.CharField()
field2 = forms.CharField()
class Meta:
model = AppModel
fields = ['field2', 'field1']
As simple as that.
I've used this to move fields about:
def move_field_before(frm, field_name, before_name):
fld = frm.fields.pop(field_name)
pos = frm.fields.keys().index(before_name)
frm.fields.insert(pos, field_name, fld)
This works in 1.5 and I'm reasonably sure it still works in more recent versions.
To add something, you can use this (Django 3+):
class ...(forms.ModelForm):
field = ...
class Meta:
model = Xxxxxx
fields = '__all__'
field_order = ['field', '__all__']
__all__ works
It has to do with the meta class that is used in defining the form class. I think it keeps an internal list of the fields and if you insert into the middle of the list it might work. It has been a while since I looked at that code.
None of these answers worked for me, Actually, you do not have to do anything custom, you can just order the fields in the order you want in your Model class. For eg ... the below code
from django.db import models
class Student(models.Model):
class Meta:
verbose_name_plural = "categories"
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=300)
nick_name = models.CharField(max_length=300)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
Your admin interface for model Will display the fields exactly in the same order in which you have declared in this case it will be (id, name, nick_name )
The order of the fields in the form depends on the order of the enumeration in the View , tested in Django 4.0.5.
class Sec_CreateView(CreateView):
model = Sec
template_name = 'forms/sec_create.html'
fields = ['rto', 'ssid', 'lic', 'IPv4', 'vlans']

Categories