Django: programatic fields in inlines - python

Expanding from this question: Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?, could it be possible to do something like this:
class MyModelInline(admin.StackedInline):
model = MyModel
extra = 1
fields = ('my_field',)
def my_field(self, obj):
return obj.one_to_one_link.my_field
If something like this were possible, it would solve most of my current Django problems, but the code above does not work: Django (rightly) complains that my_field is not present in the form.

You can do that, but you must also add my_field to your MyModelInline class's readonly_fields attribute.
fields = ('my_field',)
readonly_fields = ('my_field',)
From the docs:
The fields option, unlike list_display, may only contain names of fields on the model or the form specified by form. It may contain callables only if they are listed in readonly_fields.
If you need the field to be editable, you should be able to do that with a custom form but it takes more work to process it.

Related

Django forms.Form reflecting property field constraints

I'm implementing a Django Form witch should contain the field 'fieldA' from modelA:
class ModelA(models.Model):
fieldA = models.CharField(max_length=8)
...
My question is: Is there a way to have Django form, which will automatically handle validation of fieldA (check the max_length)? I know I Could use form.ModelFormclass referring to ModelA, but then the form would reflect all the fields of the ModelA. I would like to use simple forms.Form.
I'm looking for a solution like:
class formX(forms.Form):
fieldA = forms.CharField(**modelA.fieldA.constraints)
fieldB = ... some other fields not related to ModelA ...
.... even more fields
Maybe this question is an XY problem, but let me try...
Direct question: get field constraints from existing model
from django import forms
from django.db import models
class Foo(models.Model):
x = models.CharField(max_length=30)
y = models.IntegerField(null=True)
class FooForm(forms.Form):
foo_x = forms.CharField(max_length=Foo._meta.get_field('x').max_length)
You can access the field directly in two ways:
ModelClass.field_name.field (kind of hack, ModelClass.field_name is a django.db.models.query_utils.DeferredAttribute)
ModelClass._meta.get_field('field_name') (better way, described somewhere in docs)
However, this way you have to a) update form if field constraints are added or b) specify all attributes in advance (max_length, min_length, verbose_name, blank, etc.), making declaration of FooForm.foo_x too verbose.
Alternatives
Fields subset
First of all, if you need a subset of Foo fields, use ModelForm and specify them:
class FooForm(forms.ModelForm):
class Meta:
fields = ('x',)
Now you have a form with only x field.
Add some fields
If you want to add fields to this form (that are not related to other model), do it:
class FooForm(forms.ModelForm):
another_field = forms.IntegerField()
class Meta:
fields = ('x',)
def clean_another_field(self):
data = self.cleaned_data['another_field']
if data != 42:
raise ValidationError('Not an answer!') # i18n should be here
return data
Also you can override clean and save methods for another goals, documentation explains that nicely.
Mix fields from different models
If you need fields of two different models to be present in one form, you don't. You need two separate forms in this case, plus some inter-validation logic outside of this forms maybe (as a view method, for example, or as another class that is not a form). Maybe what you need is inline formset, it doesn't represent two mixed forms, but at least has some inter-model communication.
I found a way how to achieve a validation of form Field reflecting constrains from model Field.
class Foo(models.Model):
x = models.CharField(max_length=30)
y = models.IntegerField(null=True)
class FooForm(forms.Form):
foo_x = forms.CharField(validators=Foo._meta.get_field('x').validators)
Like this, the form will respect the max_length validator of attribute x or any other validator defined.

How should I improve my styling here (python class, Django models)?

My main model class looks like this:
class Article(models.Model):
title = models.CharField(max_length=120)
content = models.TextField()
authors = models.TextField(max_length=200)
year = models.IntegerField()
form = models.TextField(choices=FORMS)
language = models.TextField(choices=LANGUAGES)
region = models.TextField(choices=REGIONS)
tags = models.TextField()
def __str__(self):
return self.title
And, for my api to send backend data to front end, I have a serializers class that looks like this:
class ArticleSerializers(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('id', 'title', 'content', 'authors', 'year'...)
Clearly, the current way of writing fields as a bunch of hard-coded strings is very clumsy and prone to mistake (since I might change the fields in Article class but forget to update the fields in the serializer).
So my question is, how can I improve the style of fields?
Also, another side question, what is this type of coding rule / principle called, when you try to make one part of the code to be dependent on the other, so you need to change one part, instead of always having to remember changing both parts?
Thanks!
The documentation on specifying which fields to include [drf-doc] says:
You can also set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
We thus can include all fields with:
class ArticleSerializers(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
If you want to exclude certain fields, you can work with an exclude option:
You can set the exclude attribute to a list of fields to be excluded from the serializer.
So if you want to exclude the tags for example, you can work with:
class ArticleSerializers(serializers.ModelSerializer):
class Meta:
model = Article
exclude = ['tags']
Clearly, the current way of writing fields as a bunch of hard-coded strings is very clumsy and prone to mistake
Normally the metaclass logic will raise an exception if it can not find that field when it constructs the class, so when you start the application. This thus means that typos will not result in any problems. The only thing that could go wrong is specifying another field that exists for the given model.

Django - generic ModelAdmin definition

This is a newbie question.
I have multiple models that I would like to show in admin view. I would like to show all model's fields when viewing a list of records for a model.
I'd like to define a generic ModelAdmin subclass which will be able to list out all fields for a model. But not sure how to pass the class objects to it. admin.py looks like this
class ModelFieldsAdmin(admin.ModelAdmin):
def __init__(self, classObj):
self.classObj = classObj
self.list_display = [field.name for field in classObj._meta.get_fields()]
admin.site.register(Candy, ModelFieldsAdmin)
admin.site.register(Bagel, ModelFieldsAdmin)
How can I pass the classObj (which would be one of the models Candy or Bagel) , and also ensure that the get_list_display() picks up the value from self.list_display ?
This isn't the right approach. You shouldn't override __init__ for an admin class.
Rather, define get_list_display and access self.model:
def get_list_display(request):
return [field.name for field in self.model._meta.get_fields()]

Django dynamic filterset

So I want to create a django filters.FilterSet from django-filter module, but I want to dynamically add its attributes. For example, if I wanted to add SubName dynamically:
class UsersInfoFilter(filters.FilterSet):
Name=NumberFilter(lookup_type='gte')
def __new__(self):
self.SubName=NumberFilter(lookup_type='gte')
self.Meta.fields.append('SubName')
class Meta:
model = UsersInfo
fields = ['UserID', 'LanguageID', 'Name']
The problem is that FilterSet is a metaclass that immediately runs once the class has been figured out, so there is nowhere before that point that items can be dynamically added.
I've tried putting a function in as a parameter around filters.FilterSet class UsersInfo(AddObjects(filters.FilterSet)) which returns exactly what is passes, but I cannot reference UsersInfoFilter at that point since it still isn't finished being created.
I also tried making UsersInfoFilter its own base class, and then creating a class RealUsersInfoFilter(UsersInfoFilter, filters.FilterSet) as my actual filter, but then FilterSet just throws warnings about missing attributes named as fields.
There doesn't seem to be any kind of constructor function for classes in python. I'm assuming I have to do some kind of magic with metaclasses, but I've tried every combination I can think of and am at wits end.
You can't change Meta subclass from the __init__ method... there are 2 options to approach your issue...
First one - define "wide" filter on all of the model fields:
class UsersInfoFilter(filters.FilterSet):
class Meta:
model = UsersInfo
It will create default filters for all your model fields.
Second, define dynamic fields:
class UsersInfoFilter(filters.FilterSet):
name = NumberFilter(lookup_type='gte')
def __init__(self):
super(UsersInfoFilter, self).__init__()
base_filters['subname'] = NumberFilter(name='subname', lookup_type='gte')
class Meta:
model = UsersInfo
fields = ['user_id', 'language_id', 'name']
(I do not know if this is something you really want - because despite "dynamic" adding field - it should be declared as static - there are no logic here)
p.s.
why CamelCase on properties and fields? use proper pep-8.
To dynamically choose the fields in the FilterSet, I suggest to create a FilterSet factory like this:
def filterset_factory(model, fields):
meta = type(str('Meta'), (object,), {'model': model, 'fields': fields})
filterset = type(str('%sFilterSet' % model._meta.object_name),
(FilterSet,), {'Meta': meta})
return filterset
And then use it like:
DynamicFilterClass = filterset_factory(model=MyModel, fields=[...])
dynamic_filter = DynamicFilterClass(request.GET, queryset=instances)

Django Different form validation when adding and changing instance

My question is that can we perform different form validation on the same form based on different action, such as adding or changing.
Say I have a model which has a field named 'type'. And 'type' is an enum field whose choices are 'Manual' and 'Auto'. When adding a new instance, I don't want the user able to set 'type' to 'Auto'. However, the user would view and modify the instance whose 'type' is 'Auto'.
Maybe I don't need validation to do this. So if there is any other option, I' like to hear.
Thanks in advance.
Given the model:
class Spam(models.Model):
foo_type = models.WhateverFieldType(editable=False, choices=FOO_TYPE_CHOICES)
# ... other fields
Use this form (which excludes the foo_type field (disallows editing that field using the form and also disables validation of the field - it's basically, "here, edit anything on this Spam instance except foo_type"):
class SpamForm(forms.ModelForm):
class Meta:
exclude = ('foo_type',)
Then, create a subclass of UpdateView and one of ListView and update the queryset to include only users who's tye_type is set to Auto.
class SpamUpdateView(UpdateView):
def get_queryset(self):
return super(SpamUpdateView, self).get_queryset().filter(foo_type=FOO_TYPE_AUTO)
class SpamListView(ListView):
def get_queryset(self):
return super(SpamListView, self).get_queryset().filter(foo_type=FOO_TYPE_AUTO)

Categories