how to find associated Django ModelForm given the Model - django

I have dozens of Models, each with ONE associated ModelForm (whose Meta.model refers to the Model in question).
E.g.
class FooModel(Model):
pass
class FooModelForm(ModelForm):
class Meta:
model = FooModel
# current approach using a classmethod
FooModelForm.insert_in_model() # does cls.Meta.model.form = cls
So, obviously, it's easy to find FooModel given FooModelForm. What I want is to know the best way to do the REVERSE: find FooModelForm when I am presented with FooModel or even the string "Foo".
Assume only one ModelForm for each model, although solutions that return multiple are fine.
My current approach is to stash the model in the form class (as shown above), but I'm interested in knowing better approaches especially ones that could compute it centrally (without the final line above).
EDIT: I've reviewed things like Django: Display Generic ModelForm or predefined form but I believe this is a simpler question than those. The Django admin code must do something along the lines of what I seek. But get_model equivalent for ModelForms? suggests that might be voodoo and that it would be best to just do dict['Foo']=FooModelForm or its equivalent to keep track of the association explicitly. Seems repetitious.

If you have under 20 forms, sounds like mapping out a dictionary is the easiest way. Django does this kinda thing internally too.
For ModelForms, django admin just creates them on the fly via modelform_factory, so there is no comparable method to get_model
I do see, your method is bullet proof, but requires a line in ever model def.
If you only have one ModelForm per model, you could potentially iterate through the ModelForm subclasses until you find your form.
find FooModelForm when I am presented
with FooModel or even the string
"Foo".
modelforms = forms.ModelForm.__subclasses__()
def get_modelform(model):
try:
return filter(lambda x:x.Meta.model == model, modelforms)[0]
except IndexError:
print "apparently, there wasn't a ModelForm for your model"
If you want to pull the ModelForm as a string, you'll need to make sure both
app_label and __name__ are correct, which means it will be easier to use get_model('app', 'model') in the function.
You could combine this with your method and automatically place an attribute on your models that point to its ModelForm.
Hook into the class_prepared signal at the top of your apps, find the corresponding ModelForm and attach it to your Model class.
Hope that helps or gives you some ideas.

Related

Django-rest-framework serializer makes a lot of queries

Let's say I have this model:
class Place(models.Model):
....
owner = ForeignKey(CustomUserModel)
....
And I have this DRF serializer that returns a list of Places (the view calling it uses DRF's generics.ListAPIView class):
class PlaceSerializer(serializers.ModelSerializer):
owner = UserModelSerializer() # Gets only specific fields for a place owner
class Meta:
model = Place
The problem is, when the serializer gets a query that returns, let's say... 50 places, I can see (in connection.queries) that a query is being made for each owner foreign key relation, which sums up to a lot of queries. This of course has a big impact on performance.
Also important to mention is that for the view calling the serializer I had get_queryset() return only Places that are in a certain distance from a center point using a custom query. I used Django's extra() method for that.
I have tried using select_related and prefetch_related with the query mentioned above, but it doesn't seem to make any difference in terms of queries being made later on by the serializer.
What am I missing?
select_related will work as expected with serializers.
Make sure you're setting that in the 'queryset' attribute on the view if you're using the generic views.
Using select_related inside 'get_queryset' will work too.
Otherwise the only thing I can suggest is trying to narrow the issue down with some more debugging. If you still believe there's an issue and have a minimal example that'll replicate it then raise the issue as a ticket, or take the discussion to the mailing list.

django: how to extend an existing model

there are plenty of examples the tell you how to extend the user model BUT I cannot find a real, complete and documented example on how to extend an existing model without having to follow the "user profile pattern" (and honestly I wonder why).
In short, my use case is the following: I need to extend django-lfs's product model.
In LFS is registered like this (in lfs.catalog.admin):
from django.contrib import admin
[...]
from lfs.catalog.models import Product
[...]
class ProductAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name", )}
admin.site.register(Product, ProductAdmin)
[...]
I tried to register mine (that subclasses it) but I got:
django/contrib/admin/sites.py",
line 78, in register
raise AlreadyRegistered('The model %s is already registered' %
model.name)
So, someone suggested me that I have to unregister that object and register mine.
I did it like this:
from lfs.catalog.models import Product
from lfs.catalog.admin import ProductAdmin
admin.site.unregister(Product)
from lfs_product_highlights.catalog.models import Product
admin.site.register(Product,ProductAdmin)
No errors this time BUT there's no change, my custom fields are nowhere to be seen.
Any hints?
The reason why it's difficult is because of the object-relational impedance mismatch (love that phrase). Objects and classes do not map perfectly onto relational databases: ORMs like Django's attempt to smooth out the edges, but there are some places where the differences are just too great. Inheritance is one of these: there is simply no way to make one table "inherit" from another, so it has to be simulated via Foreign Keys or the like.
Anyway, for your actual problem, I can't really see what's going on but one possible way to fix it would be to subclass ProductAdmin as well, and specifically set the model attribute to your subclassed model.

get_model equivalent for ModelForms?

I have a multiple ModelForm classes that each represent a different Model. I would like to have a generic 'create' function that loads the specified model form based on a URL parameter. It is possible to load a model dynamically with this:
model_name = 'TestModel'
m = get_model('AppLabel', model_name)
Does anyone know how I can achieve the same for ModelForms, something like:
modelform_name = 'TestModelForm'
f = get_form('AppLabel', modelform_name)
if f.is_valid():
...
I can not think of a way to do this with generic views - they require the ModelForm to be passed, rather than just its name. If I get the model with get_model then pass that to the generic view it will display a form but I am unable to exclude model fields.
TIA for any tips
When you create a ModelForm it does not register itself with its model's app. (Based on experience and a quick browse through the source).
Here are some otheroptions I can think of:
All ModelForm classes exist in a single module: Use getattr on that module based on the string.
ModelForm's are spread out among many models and you have a reasonable (<30) amount of forms:
Create a dictionary mapping from form strings you expect to ModelForm classes. For example:
from some_app.forms import FirstModelForm
from another_app.forms import SecondModelForm
from additional_app.forms import FirstModelForm as AdditionalAppFirstModelForm # Will allow for managing conflicting names easily.
form_mapping = {
'FirstModelForm': FirstModelForm,
'SecondModelForm': SecondForm,
'AdditionalAppFirstModelForm': AdditionalAppFirstModelForm,
}
request_form_class = request.POST.get('form_class')
f = form_mapping.get(request_form_class)(request.POST)
if f.is_valid():
f.save()
You're dealing with a lot of forms: Create a baseclass for your ModelForm, or replace the BaseModelFormMetaclass at runtime. You'll have to deal with issues such as name conflicts, duplicate ModelForms for the same Model "automagically", so prepare for some headaches. It would be pretty rad if you could pull it off.
Personally (as you can probably see), I'd just go with option #2.
An alternate method for this is to replace forms.py with a package called forms. Then, in __init__.py within that package, import all your ModelForms.
Then you can use sdolan's Option #1.

Tutorial about how to write custom form fields in django?

Is there any good articles that explain custom form fields in django, not custom model fields? I couldn't find any through google.
Form fields are easy to customize:
class UpperCaseField(forms.CharField):
def clean(self, value)
try:
return value.upper()
except:
raise ValidationError
basically you just create a class that inherits from the field that most resembles what you want, then rewrite the clean() method so that it returns the value you want. Here is another example:
class MyObjectField(forms.ModelChoiceField):
# in this case, 'value' is a string representing
# the primary key of a MyObject
def clean(self, value):
try:
return MyObject.objects.get(pk=value)
except:
raise ValidationError
custom widgets on the other hand, are a little more useful, but a little more hard to do because there are a few more methods that need to be written so that they work smoothly.
As always with open-source code, you'll learn a great deal by reading the source itself. See the django.forms.fields module to see how all the different form fields are defined - most of them are subclasses of others already, so you can just replicate that and change what you need.
It's not a tutorial, but django's docs talks about this a little:
If the built-in Field classes don't
meet your needs, you can easily create
custom Field classes. To do this, just
create a subclass of
django.forms.Field. Its only
requirements are that it implement a
clean() method and that its __init__()
method accept the core arguments
mentioned above (required, label,
initial, widget, help_text).
You can read about the clean method and see an example at the django docs. Again, not a tutorial, but useful.
I find I am learning a lot by reading the code in some of the the django app projects that are available, such as django-extensions, which override the form fields and are good learning tools (for me, at least). This can help get you started.

How do you modify the default widget for all builtin form fields of a certain type in Django?

This is a follow-up on How do you change the default widget for all Django date fields in a ModelForm?.
Suppose you have a very large number of models (e.g. A-ZZZ) that is growing with the input of other developers that are beyond your control, and you want to change the way all date fields are entered (i.e. by using jQueryUI). What's the best way to ensure that all date fields are filled out using that new widget?
One suggestion from the cited question was:
def make_custom_datefield(f):
if isinstance(f, models.DateField):
# return form field with your custom widget here...
else:
return f.formfield()
class SomeForm(forms.ModelForm):
formfield_callback = make_custom_datefield
class Meta:
# normal modelform stuff here...
However, is this possible to do where you don't have explicit ModelForm's, but url patterns come from models directly? i.e. your url config is likeso:
url(r'^A/?$', 'list_detail.object_list', SomeModelA)
where SomeModelA is a model (not a form) that's turned into a ModelForm by Django in the background.
At present in my system there are no Forms for each Model. The only point of creating forms explicitly would be to add the formfield_callback suggested in the prior solution, but that goes against DRY principles, and would be error prone and labour intensive.
I've considered (as suggested in the last thread) creating my own field that has a special widget and using that instead of the builtin. It's not so labour intensive, but it could be subject to errors (nothing a good grep couldn't fix, though).
Suggestions and thoughts are appreciated.
It sounds like you want to do this project-wide (ie: you're not trying to do this in some cases, but in ALL cases in your running application).
One possibility is to replace the widget attribute of the DateField class itself. You would need to do this in some central location... something that is guaranteed to be loaded by every running instance of the django app. Middleware can help with this. Otherwise, just put it in the __init__ file of your app.
What you want to do is re-assign the widget property for the forms.DateField class itself. When a new DateField is created, Django checks to see if the code specifies any particular widget in the field property definition. If not, it uses the default for DateField. I'm assuming that if a user in your scenario really defined a particular widget, you'd want to honour that despite the change to your global API.
Try this as an example of forcing the default to some other widget... in this case a HiddenInput:
from django import forms
forms.DateField.widget = forms.HiddenInput
class Foo(forms.Form):
a = forms.DateField()
f = Foo()
print f.fields['a'].widget
# results in <django.forms.widgets.HiddenInput object at 0x16bd910>