django ModelForm without all fields - fill them automatically - python

I'd like to fill one field of my model automatically. It holds a client IP.
I've defined an CreateView as follows:
class MyView(CreateView):
def post(self, request, *args, **kwargs):
self.form_class.client_ip = request.META.get('REMOTE_ADDR')
super(MyView, self).post(request, *args, **kwargs)
model = MyModel
form_class = MyForm
and MyForm in that way:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ('ip',)
And I have no idea how to fill this exluded field.

In MyView you should add a method called get_initial which returns the initial values of the form (as a dictionary). For example:
def get_initial(self):
return { 'ip': ... }
These initial values are then used when the form is created.

excluded fields have be to manually filled. you can look into the middleware processors that can add to the request.POST dict and you can override the __init__ on the MyForm, [first call super] and then set the model's ip field.
the middleware processor can also add to POST some other request level attributes, making it available for future use.

Related

Django custom model save() method for a non-persisted attribute

In Django 4 custom model save() method, how do you pass a form non-persistent form value?
For example:
The model form below has the non-persistent field called client_secret.
class ClientModelForm(forms.ModelForm):
client_secret = forms.CharField(initial=generate_urlsafe_uuid)
This field will never be saved, it is auto-generated and will be required to make a hash for a persisted field in my model save() method.
class Client(models.Model):
client_hash = models.BinaryField(editable=False, blank=True, null=True)
def save(self, *args, **kwargs):
""" Save override to hash the client secret on creation. """
if self._state.adding:
"GET THE CLIENT SECRET FROM THE FORM"
client_hash = make_hash_key(client_secret)
self.client_hash = client_hash
How do I get the client secret value from the form above code example? Is this the most appropriate approach?
You would need to call the super class' save method at the end like so:
super(Client, self).save(*args, **kwargs)
For saving the form, you would need to add a model to the modelform's Meta class, and you would need to add the following logic into a view:
form = Form(request.METHOD)
if form.is_valid():
uuid = form.instance.client_secret
form.save()
Where form would look like:
class ClientForm(forms.ModelForm):
fields...
class Meta:
model = ClientModel
Highly recommend taking a look at the docs
Edit
To get the logic into the model save, you could pass it into the kwargs of the save, save(client_secret=client_secret), and then in the save method you could try:
self.field = kwargs.get('client_secret')
then when saving the form, as shown above, you could do
save(client_secret=uuid)

Accessing inline fields within inline get_formset in Django Admin

I have the following example where I have buildings(address, location,...) and apartments(name, size, type, building). One building containing multiple apartments.
class BuildingAdmin(admin.ModelAdmin):
inlines = [ApartmentInline,]
class ApartmentInline(admin.StackedInline):
def get_formset(self, request, obj=None, **kwargs):
formset = super(ApartmentInline, self).get_formset(request, obj=None, **kwargs)
#Here i'd like to see the values of inline fields, for example size or building that.
#Similar to how one can access ModelAdmin fields with obj.location within get_form
formset.form.base_fields["type"].widget = SelectMultiple(choices=custom_choices)
return formset
I'd like to be able to get the current apartments instance and field values when editing the object (for example size), so that I can create custom choices (querying other DB's or API's) for another field (type).
To modify the inline form widget you can override ModelInlineForm which gives each inline instance access after initializing.
from django.contrib import admin
from django.urls import resolve
class ApartmentInlineForm(forms.ModelForm):
class Meta:
model = A
fields = (
'...',
'...',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# calculate widget choices
self.fields['type'].widget = SelectMultiple(choices=custom_choices)
class ApartmentInline(admin.StackedInline):
model = Apartment
form = ApartmentInlineForm

Pass argument to Django form

There are several questions on stackoverflow related to this topic but none of them explains whats happening neither provides working solution.
I need to pass user's first name as an argument to Django ModelForm when rendering template with this form.
I have some basic form:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.first_name = ???
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
fields = [***other fields***, 'first_name']
Here's my sample class-based view:
class SomeClassBasedView(View):
def get(self, request):
user_first_name = request.user.first_name
form = MyForm(user_first_name)
return render(request, 'my_template.html', {'form': form})
What do I pass to MyForm when initialising it and how do I access this value inside the __init__ method?
I want to use 'first_name' value as a value for one of the fields in template by updating self.fields[***].widget.attrs.update({'value': self.first_name}).
The solution is as simple as passing initial data dictionary to MyForm for fields you need to set initial value.
This parameter, if given, should be a dictionary mapping field names to initial values. So if MyModel class has a field my_field and you want to set its initial value to foo_bar then you should create MyForm object with initial parameters as follows:
form = MyForm(initial={'my_field': 'foo_bar'})
A link to Django documentation on this topic.
You can pass the parameter instance to your form like this:
obj = MyModel(...)
form = MyForm(instance=obj)
If obj has a user first name it will be attached to your form.

different validation in drf serializer per request method

Lets say i have a model like so:
class MyModel(models.Model):
first_field = models.CharField()
second_field = models.CharField()
and an API view like so:
class MyModelDetailAPI(GenericAPIView):
serializer_class = MyModelSerializer
def patch(self, request, *args, **kwargs):
# Do the update
def post(self, request, *args, **kwargs):
# Do the post
The first_field is a field that is only inserted in the POST method (and is mandatory) but on each update, the user can't change its value so the field in the PATCH method is not mandatory.
How can i write my serializer so that the first_field is required on POST but not required on PATCH. Is there any way of dynamically setting the required field so i can still use the DRF validation mechanism? Some sort of validator dispatcher per request method?
I want something like this for example:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = {
'POST': ['first_field']
'PATCH': []
}
I need more space than comments provide to make my meaning clear. So here is what I suggest:
Different formatting means different serializers.
So here you have, for instance a MyModelSerializer and a MyModelCreationSerializer. Either create them independently, or have one inherit the other and specialize it (if it makes sense).
Use the appropriate GenericAPIView hook to return the correct serializer class depending on self.action. A very basic example could be:
class MyModelDetailAPI(GenericAPIView):
# serializer_class = unneeded as we override the hook below
def get_serializer_class(self):
if self.action == 'create':
return MyModelCreationSerializer
return MyModelSerializer
Default actions in regular viewsets are documented here, they are:
create: POST method on base route url
list: GET method on base route url
retrieve: GET method on object url
update: PUT method on object url
partial_update: PATCH method on object url
destroy: DELETE method on object url

How to show hidden autofield in django formset

A Django autofield when displayed using a formset is hidden by default. What would be the best way to show it?
At the moment, the model is declared as,
class MyModel:
locid = models.AutoField(primary_key=True)
...
When this is rendered using Django formsets,
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = ('locid', 'name')
it shows up on the page as,
<input id="id_form-0-locid" type="hidden" value="707" name="form-0-locid"/>
Thanks.
Edit
I create the formset like this -
LocFormSet = modelformset_factory(MyModel)
pformset = LocFormSet(request.POST, request.FILES, queryset=MyModel.objects.order_by('name'))
Second Edit
Looks like I'm not using the custom form class I defined there, so the question needs slight modification..
How would I create a formset from a custom form (which will show a hidden field), as well as use a custom queryset?
At the moment, I can either inherit from a BaseModelFormSet class and use a custom query set, or I can use the ModelForm class to add a custom field to a form. Is there a way to do both with a formset?
Third Edit
I'm now using,
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
locid = forms.IntegerField(min_value = 1, required=True)
self.fields['locid'].widget.attrs["type"] = 'visible'
self.queryset = MyModel.objects.order_by('name')
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet()
But this still doesn't
Show locid
Use the custom query that was specified.
Try changing the default field type:
from django import forms
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
class Meta:
model = MyModel
fields = ('locid', 'name')
EDIT: Tested and works...
As you say, you are not using the custom form you have defined. This is because you aren't passing it in anywhere, so Django can't know about it.
The solution is simple - just pass the custom form class into modelformset_factory:
LocFormSet = modelformset_factory(MyModel, form=MyModelForm)
Edit in response to update 3:
Firstly, you have the redefinition for locid in the wrong place - it needs to be at the class level, not inside the __init__.
Secondly, putting the queryset inside the form won't do anything at all - forms don't know about querysets. You should go back to what you were doing before, passing it in as a parameter when you instantiate the formset. (Alternatively, you could define a custom formset, but that seems like overkill.)
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['locid'].widget.attrs["type"] = 'visible'
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet(request.POST, request.FILES,
queryset=MyModel.objects.order_by('name')))
Okay, none of the approaches above worked for me. I solved this issue from the template side, finally.
There is a ticket filed (http://code.djangoproject.com/ticket/10427), which adds a "value" option to a template variable for a form. For instance, it allows,
{{form.locid.value}}
to be shown. This is available as a patch, which can be installed in the SVN version of django using "patch -p0 file.patch"
Remember, the {{form.locid.value}} variable will be used in conjunction with the invisible form - otherwise, the submit and save operations for the formset will crash.
This is Not the same as {{form.locid.data}} - as is explained in the ticket referred to above.
The reason that the autofield is hidden, is that both BaseModelFormSet and BaseInlineFormSet override that field in add_field. The way to fix it is to create your own formset and override add_field without calling super. Also you don't have to explicitly define the primary key.
you have to pass the formset to modelformset_factory:
LocFormSet = modelformset_factory(MyModel,
formset=VisiblePrimaryKeyFormSet)
This is in the formset class:
from django.forms.models import BaseInlineFormSet, BaseModelFormSet, IntegerField
from django.forms.formsets import BaseFormSet
class VisiblePrimaryKeyFormset(BaseModelFormSet):
def add_fields(self, form, index):
self._pk_field = pk = self.model._meta.pk
if form.is_bound:
pk_value = form.instance.pk
else:
try:
pk_value = self.get_queryset()[index].pk
except IndexError:
pk_value = None
form.fields[self._pk_field.name] = IntegerField( initial=pk_value,
required=True) #or any other field you would like to display the pk in
BaseFormSet.add_fields(self, form, index) # call baseformset which does not modify your primary key field

Categories

Resources