Django ModelForm Meta - python

I want create a ModelForm class where model is a parameter passed from the view.(i want a dynamic form, so i can create all forms using the same class ObjectForm by just changing model value in Meta) :
class ObjectForm(ModelForm):
model_name = None
def __init__(self, *args, **kwargs):
model_name = kwargs.pop('model_name ')
super(ModelForm, self).__init__(*args, **kwargs)
class Meta:
model = models.get_model('core', model_name )
exclude = ("societe")
An error is occured and say that model_name is not a global field.
Please help me on this problem.

your problem is that the class (and the Meta class) are processed at compile time, not when you instantiate your ObjectForm. at compile time, the model name is unknown. creating classes dynamically is possible, but a bit more complicated. as luck has it, the django devs have done the hard work for you:
>>> from django.forms.models import modelform_factory
>>> modelform_factory(MyModel)
<class 'django.forms.models.MyModelForm'>
update
So you want something like
def my_view(request):
# ...
MyForm = modelform_factory(MyModel)
form = MyForm(request.POST) # or however you would use a 'regular' form

Well, your basic error is that you are accessing model_name as a local variable, rather than as a model instance. That's fairly basic Python.
But even once you've fixed this, it still wouldn't work. The Meta class is evaluated at define time, by the form metaclass, rather than at runtime. You need to call forms.models.modelform_factory - you can pass in your modelform subclass to the factory, if you want to define some standard validation and/or fields.
form_class = modelform_factory(MyModel, form=MyModelForm)

Related

The order of inherited classes matters in Python?

I came across this error in my django application after hitting submit on a create or edit form:
No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model..
This was confusing because I have a get_success_url passed down through inheritance. To be clear, I have found the issue, but have no earthly idea why my solution worked.
Here was the code causing the error inside
.../views.py:
class FormViews():
model = Ticket
form_class = TicketForm
def get_success_url(self):
return reverse('tickets:index')
class TicketCreate(CreateView, FormViews):
template_name = 'tickets/ticket_create_form.html'
model = Ticket
form_class = TicketForm
class TicketUpdate(UpdateView, FormViews):
model = Ticket
form_class = TicketForm
template_name_suffix = '_update_form'
I created the FormViews class so there would not be any repeated code for the model, form_class, and get_success_url.
I was able to resolve this error by switching the parameters in my function definitions:
class TicketCreate(CreateView, FormViews) became class TicketCreate(FormViews, CreateView)
class TicketUpdate(UpdateView, FormViews) became class TicketUpdate(FormViews, UpdateView)
This fixed it. Now I redirect to the index page without any issues. Why is it that the get_success_url is recognized after switching the listed parent classes? I would have thought that the attributes and functions are inherited and recognized by Django regardless of order. Is this a Python or Django related issue?
In python every class has something called an MRO (Method Resolution Order), this explains it pretty well. Your FormViews (also for the most part classes in python are singular) is more of a mixin, I would call it as such: FormViewMixin.
Since CreateView and UpdateView are proper classes that have get_success_url defined, the order ABSOLUTELY matters. So I would put the things you want "discovered", first.
class TicketCreateView(FormViewMixin, CreateView):
...
is what you want.

How to add link fields to serializer dynamically

I would like to create a general serializer for a ManyToMany link, which will include linked models data.
from rest_framework import serializers
def get_model_serializer(model, fields_to_serialize):
class BaseSerializer(serializers.ModelSerializer):
class Meta:
model = model
fields = fields_to_serialize
return BaseSerializer
def get_many_to_many_serializer(many_to_many_model, first_model, second_model, fields_to_serialize)
serializer = get_model_serializer(many_to_many_model, fields_to_serialize)
class BaseSerializer(serializer):
pass
# only when I directly set the attributes upon class definition it works
#attendee = get_model_serializer(first_model)()
#operation = get_model_serializer(second_model)()
# This does not work
setattr(BaseSerializer, first_model.__name__.lower(), get_model_serializer(first_model)())
setattr(BaseSerializer, second_model.__name__.lower(), get_model_serializer(second_model)())
#Or this
#setattr(BaseSerializer, 'operation', get_model_serializer(first_model)())
#setattr(BaseSerializer, 'attendee', get_model_serializer(second_model)())
return BaseSerializer
The problem is, that when I set the attribute using setattr, the linked models are not serialized. I guess there is some magic upon class creation or whatever?
Any ideas, how can I go around this?
You can use the three argument form of type to dynamically create new types/classes
type('M2MSerializer', (serializer, ), {
first_model.__name__.lower(): get_model_serializer(first_model)(),
first_model.__name__.lower(): get_model_serializer(first_model)()
})

Django: Can't use session vars in ModelForm

I have this session var:
empresa=request.session['codEmp']
I have a ModelForm with a ModelChoiceField:
class AuxiForm(forms.ModelForm):
tipAux = forms.ModelChoiceField(queryset=TipoAux.objects.all(), empty_label=None,
required=True, to_field_name='codigo')
If you see the queryset for tipAux it's currently calling for all the TipoAux objects, but I really need to do a filter, calling only the TipoAux objects that have the same empresa attribute that the user has on his codEmp session var.
I tried by doing:
tipAux = forms.ModelChoiceField(queryset=TipoAux.objects.filter(empresa=request.session['codEmp']),
empty_label=None, required=True,
to_field_name='codigo')
But Django is not allowing me to use request.session in the form.
Any way to handle this?
Try overriding the form's __init__() method and pass the session variable in as an argument to the form:
class AuxiForm(forms.ModelForm):
def __init__(filter_on, *args, **kwargs):
super(AuxiForm, self).__init__(*args, **kwargs)
self.fields['tipAux'] = forms.ModelChoiceField(
queryset=TipoAux.objects.filter(empresa=filter_on),
empty_label=None,
required=True,
to_field_name='codigo'
)
And, in your view.py, you could say something like: form = AuxiForm(data=request.POST, filter_on=request.session['codEmp'])
Alternatively, you could use Django's generic model view. If your form isn't more complicated than the one you posted—you'd just set the queryset attribute. (I'll leave the reading to you because I'm only just getting familiar with generic views myself.)

Creating a display-only (non-editable) Django admin field

Is it possible to build a custom model field/widget combination which displays a value but never writes anything back to the database? I would use this widget exclusively in the admin's forms.
I wrote my own field, which overwrites the formfield() method to declare its own widget class. It displays just fine, but as soon as the 'Save' button is clicked in the admin, I'm getting a validation error:
This field is required.
That makes sense, considering that my widget didn't render out a form field. However, what I'd like to do is basically remove this field from the update process: whenever used in the admin, it just shouldn't be mentioned in the SQL UPDATE at all.
Is that possible?
Here's a sketch of the code I have so far:
class MyWidget(Widget):
def render(self, name, value, attrs=None):
if value is None:
value = ""
else:
# pretty print the contents of value here
return '<table>' + ''.join(rows) + '</table>'
class MyField(JSONField):
def __init__(self, *args, **kwargs):
kwargs['null'] = False
kwargs['default'] = list
super(MyField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {
'form_class': JSONFormField,
'widget': MyWidget,
}
defaults.update(**kwargs)
return super(MyField, self).formfield(**defaults)
UPDATE 1: The use case is that the field represents an audit log. Internally, it will be written to regularly. The admin however never needs to write to it, it only has to render it out in a very readable format.
I'm not using any other ModelForms in the application, so the admin is the only form-user. I don't want to implement the behavior on the admin classes themselves, because this field will be reused across various models and is always supposed to behave the same way.
There are multiple ways to create a read-only field in the admin pages. Your requirements on the database storage are a bit fuzzy so I go through the options.
You have to register an AdminModel first in admin.py:
from django.contrib import admin
from yourapp.models import YourModel
class YourAdmin(admin.ModelAdmin):
pass
admin.site.register(YourModel, YourAdmin)
Now you can add different behavior to it. For example you can add the list of fields shown in the edit/add page:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
This can be names of the model fields, model properties or model methods. Methods are displayed read-only.
If you want to have one field read-only explicitly add this:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
readonly_fields = ['field2']
Then you have the option to overwrite the display of the field completely by adding a method with the same name. You will not even need a model field/method with that name, then:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
readonly_fields = ['field2']
def field2(self, obj):
return '*** CLASSIFIED *** {}'.format(obj.field2)
With django.utils.safestring.mark_safe you can return HTML code as well.
All other options of the Admin are available, except the widget configuration as it applies to the writable fields only.
I might be a little confused as to what you want but you might want to look into model properties. Here is an example for my current project.
Code inside your model:
class Textbook(models.Model):
#other fields
#property
def NumWishes(self):
return self.wishlist_set.count()
Then you can just display it on the admin page.
class Textbook_table(admin.ModelAdmin):
fields = ["""attributes that are saved in the model"""]
list_display = ("""attributes that are saved in the model""", 'NumWishes'')
So now I can display NumWishes in the admin page but it doesn't need to be created with the model.
Hello in the class admin modify the permission method
#admin.register(my_model)
class My_modelAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False

Django REST Framework - pass model to ViewSet via router

I'm trying to create a REST-ModelViewSet that has no model predefined, but takes a model when registered with the router. I need this to dynamically add models to my REST-API, without configuring any new viewsets or serializers.
My idea was to pass the model in the kwargs of __init__, but I can't figure out how to correctly do this. Here is what I tried:
//Viewset
class ThemeViewSet(viewsets.ModelViewSet):
def __init__(self, **kwargs):
self.model = kwargs['model']
self.serializer_class = None
super(ThemeViewSet, self).__init__(**kwargs)
def get_serializer_class(self):
if self.serializer_class is not None:
return self.serializer_class
class ThemeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = self.model
self.serializer_class = ThemeSerializer
return self.serializer_class
//Router:
router = routers.DefaultRouter()
router.register(r'mytheme', ThemeViewSet(model=mytheme), base_name='mytheme')
Now, if I try to print self.model in __init__, it correctly shows <class 'myapp.models.mytheme'> in the console, but Django still returns an error:
AttributeError at /api/mytheme/
This method is available only on the view class.
This error is raised by the classonlymethod-decorator. I don't really know what to make of this, is there any way to pass the model to __init__, or is there a different approach that I can try?
(I know that wq.db.rest has a router that does what I want, but I don't want to use wq. I haven't tried tastypie, would that make it easier/possible?)
Thanks in advance!
Django REST Framework expects that a ViewSet class is passed into the router, not a view instance. This is because the instance has to be created for each request, which prevents a lot of ugly issues with shared state and also follows the standard Django class-based views.
You may have better luck with having a method that creates a customized ViewSet class based on the model that is passed into it:
class ThemeViewSet(viewsets.ModelViewSet):
#classmethod
def create_custom(cls, **kwargs):
class CustomViewSet(cls):
model = kwargs["model"]
queryset = kwargs["model"].objects.all()
return CustomViewSet
Note that I'm also setting the queryset for the view, and DRF no longer accepts just a model since 2.4 was released.
This will create a new class each time it is called, and the model will automatically be set to the model that is passed into it. When registering it with the router, you would do something like:
router.register(r'mytheme', ThemeViewSet.create_custom(model=mytheme), base_name='mytheme')
This way you will still be passing a ViewSet class to the router, but it will be customized for the model that is passed in. You must make sure to set the base_name, or the router won't be able to generate the view names and you will eventually run into errors.

Categories