How to change form fields' value before validation? - python

I have an FloatField in my models.py that has to store (among other things) minutes, seconds and milliseconds. It's no biggie to store them in the format of ss.ms, but I have to provide a possibility to insert them via ModelForm in the format of mm.ss.ms.
Problem is, form validation disapproves of mm.ss.ms for FloatField and I'm struggling to find a workaround.
_clean functions are great, but run after the Django validations. How to clean data from form before it gets validated by Django?
EDIT:
models.py
from django.db import models
class Result(models.Model):
date = models.DateField()
result = models.FloatField()
rank = models.IntegerField(blank=True, null=True)
forms.py
class AddResult(forms.ModelForm):
class Meta:
model = Result
fields = ('result', 'rank', 'date')
def __init__(self, *args, **kwargs):
self.profile = kwargs.pop('profile', None)
super(AddResult, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.label_class = 'form-label-div'
self.helper.field_class = 'form-field-div'
self.helper.label_class = 'col-xs-4'
self.helper.field_class = 'col-xs-8'
self.helper.layout = Layout(
Div(
Div(
'result',
css_class="col-xs-4",
),
Div(
'rank',
css_class="col-xs-4",
),
css_class="row",
),
Div(
Div(
'date',
css_class="col-xs-4",
css_id="date-picker"
),
Div(
css_class="col-xs-4",
css_id="date-picker"
),
css_class="row",
)
)
def save(self, commit=True):
result = super(AddResult, self).save(commit=False)
if self.profile:
result.profile = self.profile
result.save()
PS! Originally I wrote that I had a problematic IntegerField, but that was some kind of a brain fart. We're talking about a FloatField of course. The main question remains the same.

I would suggest you not to use models forms instead use a forms.form and in your view before the ** form.is_valid() ** try to retrieve the form fields with the help of REQUEST method.
May be this helps.

Related

How to use add_form for Django admin?

How can i correctly use add_form in admin panel when class is inherited from admin.ModelAdmin. I find out the hack with overriding get_form method where you can dynamically change form to add_form value. With current approach i'm getting this error
formsets, inline_instances = self._create_formsets(request, form.instance, change=False)
AttributeError: 'UserForm' object has no attribute 'instance'
form.py
class AddCustomProductForm(forms.Form):
users = forms.ChoiceField(
label='Select a Profile',
required=True,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["users"].choices = CustomUser.objects.all()
admin.py
class PostAdmin(admin.ModelAdmin):
list_display = ('email', 'created', 'company_id',)
add_form = AddCustomProductForm
form = CustomProductForm
fieldsets = (
(None, {"fields": ("users")}),
)
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
You need a ModelForm, where you take the values directly from a Model( in this case ModelAdmin) in a form.
https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/
The other way is make "your" admin panel.

ERROR: Cannot create a Meal instance with a FK Store instance in the views.py (Django and Django Rest Framework)

I am doing a delivery API REST usign Django and Django Rest Framework.
In the Meal app there is an ERROR that has been for days and I cannot solve it. I do not know how to fix it.
Thanks you in advance.
This is the bug shown in postman:
{
"store": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got Store."
]
}
Meal/models/meals.py
class Meal(models.Model):
'''Meals models.'''
store = models.ForeignKey(
Store,
on_delete=models.CASCADE,
)
name = models.CharField(max_length=120)
slugname = models.SlugField(
unique=True,
max_length=120,
)
description = models.TextField(max_length=300, blank=True)
price = models.DecimalField(
'meal price',
max_digits=5,
decimal_places=2
)
picture = models.ImageField(
'meal picture',
upload_to='meals/pictures/',
blank=True,
null=True,
help_text="Price max up to $999.99"
)
# Status
is_available = models.BooleanField(
'Meal available in menu',
default=True,
help_text='Show is the items is available for customers'
)
# Stats
rating = models.FloatField(
default=5.0,
help_text="Meal's rating based on client califications"
)
Meal/views/meals.py
class MealViewSet(mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
'''Meals view set.
'''
serializer_class = MealModelSerializer
lookup_field = 'slugname'
search_fields = ('slugname', 'name')
# Method call every time this MealViewSet is instanced
def dispatch(self, request, *args, **kwargs):
'''Verify that the store exists.
Add the URL input <store_slugname> to the Meal model field store(FK).
'''
store_slugname = kwargs['store_slugname']
self.store = get_object_or_404(Store, store_slugname=store_slugname)
return super(MealViewSet, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
'''Get Store's available meals'''
return Meal.objects.filter(
store=self.store,
is_available=True
)
def create(self, request, *args, **kwargs):
'''Assign Meal to a Store (received in the URL input <store_slugname>)
'''
store = self.store # Got from the dispatcher
request.data['store'] = store
serializer = MealModelSerializer(
store,
data=request.data
)
# import pdb; pdb.set_trace()
serializer.is_valid(raise_exception=True)
serializer.save()
data = serializer.data
return Response(data, status=status.HTTP_201_CREATED)
and Meal/serializers/meals.py
class MealModelSerializer(serializers.ModelSerializer):
'''Meal model serializer.'''
store = StoreModelSerializer()
class Meta:
"""Meta class."""
model = Meal
fields = (
'store',
'name',
'slugname',
'description',
'price',
'picture',
'rating'
)
read_only_fields = (
'rating',
)
The debugger in the terminal shows this:
(Store=dominospizza is a valid and previous Store created)
Pdb) store
<Store: dominospizza>
(Pdb) store.__dict__
{'_state': <django.db.models.base.ModelState object at 0x7fd539882c10>, 'id': 1, 'name': 'Peperoni large pizza', 'store_slugname': 'dominospizza', 'about': '', 'picture': '', 'pickup_address': 'Urdesa cerca de casa de Agapito', 'is_active': True, 'is_open': True, 'orders_dispatched': 0, 'reputation': 5.0}
(Pdb) request.data
{'name': 'Peperoni large pizza', 'slugname': 'LPizzaPeperoni', 'price': '9.00', 'store': <Store: dominospizza>}
First off,
Try to follow REST as much as possible. If you want to filter the backend entities based on a field value, provide them in query params. You can automate filtering using django-filters. Refer this.
Secondly,
You don't need to override the create method of the ViewSet to achieve that. Serializers are made for this. Modify the serializer code as below,
class MealModelSerializer(serializers.ModelSerializer):
'''Meal model serializer.'''
store = StoreModelSerializer()
store_id = models.PrimaryKeyRelatedField(
queryset=models.Store.objects.all(),
write_only=True,
source="store",
required=False
)
store_slogname = models.SlugRelatedField(
queryset=models.Store.objects.all(),
write_only=True,
slug_field="store_slugname"
source="store",
required=False
)
class Meta:
"""Meta class."""
model = Meal
fields = (
'store',
'name',
'slugname',
'description',
'price',
'picture',
'rating'
)
read_only_fields = (
'rating',
)
But remember, the standard practice is to pass id's instead of names. Make sure the Store field store_slugname is unique.
From the frontend, you can pass store_id or store_slugname in the POST, PUT, PATCH body. The serializer automatically looks up the DB and validates the data. You don't need to override any method.
Edit #1:
You can use django-rest-framework-filters feature AllLookupsFilter to provide all types of filtering at frontend level.
Example:
/api/{version}/meals/?store__store_slugname=<value>
/api/{version}/meals/?store__store_slugname__contains=<value>
/api/{version}/meals/?store__store_slugname__in=<value1,value2,value3>
To sum up, your viewset will look something like this,
class MealViewSet(
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet
):
'''Meals view set.'''
serializer_class = MealModelSerializer
lookup_field = 'slugname'
search_fields = ('slugname', 'name')
filter_class = MealFilter
Hope this helps!
please try to change this in MealViewSet.create:
request.data['store'] = store
by this:
request.data['store'] = store.id
This is because i think default ModelSerializer fks are solved like a primarykeyrelatedfield, then its expecting a pk not the instance.

Custom ModelForm with FormHelper re-appends buttons Div after each refresh when a layout is passed

To keep my ModelForms DRY I have created a custom ModelForm with a FormHelper so I can append a Div with a Submit and a Cancel button to the layout. It also offers the possibility to add a custom layout.
This works perfectly fine when I don't specify a custom layout, but when I do, every time I refresh the page it appends the buttons Div (this doesn't happen when there's no custom layout)
This is the custom ModelForm:
class ModelFormWithHelper(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
kwargs = self.get_helper_kwargs()
helper_class = FormHelper(self)
if 'custom_layout' in kwargs:
self.helper.layout = kwargs['custom_layout']
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-12'
self.helper.field_class = 'col-md-12'
self.helper.layout.append(
Div(
HTML('<br>'),
FormActions(
Submit('submit', 'Save'),
HTML('<a class="btn btn-secondary" href="{{ request.META.HTTP_REFERRER }}">Cancel</a>')
),
css_class='row justify-content-center',
),
)
def get_helper_kwargs(self):
kwargs = {}
for attr, value in self.Meta.__dict__.items():
if attr.startswith('helper_'):
new_attr = attr.split('_', 1)[1]
kwargs[new_attr] = value
return kwargs
And this is the ModelForm:
class CargoForm(ModelFormWithHelper):
class Meta:
model = Cargo
exclude = []
helper_custom_layout = Layout(
Div(
'name',
'order',
css_class='col-6 offset-3',
),
)
This is the form with no custom_layout after I refresh the page 3 times:
And this is the form with a custom_layout after I refresh the page 3 times:
I'm aware that I can use the self.helper.add_input method to avoid this problem, but then I would not be able to center de buttons.
I would appreciate if anyone can help me solve this re-appending problem.
Thanks in advance.
After almost having a mental breakdown, I have finally figured it out.
For anyone trying to achieve the same goal, here's the solution (I got rid of the get_helper_kwargs method since I only use helper_custom_layout)
# Put it in a variable to avoid repeating it twice
buttons_layout = Layout(
Div(
HTML('<br>'),
FormActions(
Submit('submit', 'Save'),
HTML('<a class="btn btn-secondary" href="{{ request.META.HTTP_REFERER }}">Cancel</a>')
),
css_class='row justify-content-center',
),
)
class ModelFormWithHelper(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
custom_layout = getattr(self.Meta, 'helper_custom_layout', None)
self.helper = FormHelper(self)
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-12'
self.helper.field_class = 'col-md-12'
if custom_layout:
self.helper.layout = Layout(custom_layout, buttons_layout)
else:
self.helper.layout.append(buttons_layout)
And that's it. Now you can make your custom Layouts a little bit more DRY. Hope you can find this useful.

Django: Cannot validate a form without an initial foreignkey value

I have a form in django views.py.
diagnosis.patient is a foreignkey of demographics.patient_id.
my_diagnosis form is not valid because patient is empty. Is there a way to fix this?
def input(request):
context = RequestContext(request)
print context
ret = cache.get('input-rendered')
if request.method == 'POST':
my_demographics = DemographicForm(request.POST, prefix="demo")
my_diagnosis = DiagnosisForm(request.POST, prefix='diag')
if (my_demographics.is_valid() ):
print "dem and diag validation"
my_demographics_object = my_demographics.save(commit=False)
my_demographics_object.author = request.user
my_demographics_object.save()
#my_diagnosis = DiagnosisForm(request.POST, prefix='diag', initial={'patient':my_demographics_object.patient_id} )
print "my dem id"
print my_demographics_object.patient_id
if (my_diagnosis.is_valid()):
my_diagnosis_object=my_diagnosis.save(commit=False)
my_diagnosis_object.patient = my_demographics_object.patient_id
my_diagnosis_object.author = request.user
my_diagnosis_object.save()
This is my Diagnosis form in forms.py:
class DiagnosisForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DiagnosisForm, self).__init__(*args, **kwargs)
self.fields['diagnosis_circumstances_date']= forms.DateField(label=('Date'),required=False,
widget=DateTimePicker(options={"format": "YYYY-MM-DD",
"pickTime": False,
"startDate": "1900-01-01"}))
self.helper=FormHelper(form=self)
self.fields['icd_10_desc']= forms.ModelChoiceField(queryset=icd_10.objects.all(),
widget=autocomplete_light.ChoiceWidget("icd_10Autocomplete"))
self.fields['icd_10_desc'].label = "ICD-10 description"
diagnosis_option_value = (
('b-thalassaemia syndromes', 'b-thalassaemia syndromes',),
('a-thalassaemia syndromes', 'a-thalassaemia syndromes'),
('Sickle cell syndromes', 'Sickle cell syndromes'),
('Other haemoglobin variants','Other haemoglobin variants'),
('Red cell membrane disorders','Red cell membrane disorders'),
('Red cell enzyme disorders','Red cell enzyme disorders'),
('Congenital dyserythropoietic anaemias','Congenital dyserythropoietic anaemias')
)
self.fields['diagnosis_option']=forms.MultipleChoiceField(choices=diagnosis_option_value, widget=forms.CheckboxSelectMultiple())
diagnosis_circumstances_value = (
('Antenatal diagnosis','Antenatal diagnosis'),
('Neonatal diagnosis','Neonatal diagnosis'),
('By the presence of affected related','By the presence of affected related'),
('Clinical diagnosis', 'Clinical diagnosis'),
('Other','Other')
)
self.fields['diagnosis_circumstances']=forms.MultipleChoiceField(choices=diagnosis_circumstances_value, widget=forms.CheckboxSelectMultiple())
#self.fields['patient'].queryset = Demographic.objects.filter(patient_id=self.instance.patient)
self.helper.field_class = 'col-md-8'
self.helper.label_class = 'col-md-3'
#self.helper.form_class = 'forms-horizontal'
self.helper.layout = Layout(
Fieldset (
# 'patient',
'<b>Diagnosis information</b>',
Div(
#HTML(u'<div class="col-md-2"></div>'),
Div('age_of_diagnosis',css_class='col-md-6'),
Div('age_at_onset_of_symptoms',css_class="col-md-6"),
css_class='row',
),
'diagnosis_option',
'record_of_genotype',
'icd_10_desc',
'icd_10_code',
'orpha_code',
'comment',
),
FormActions(
Submit('submit', "Save changes"),
Submit('cancel',"Cancel")
),
)
self.helper.form_tag = False
self.helper.form_show_labels = True
class Meta:
model = Diagnosis
exclude = ['patient']
exclude = ('author',)
list_display = ('title', 'pub_date', 'author')
This is the result of my_diagnosis.errors:
<ul class="errorlist"><li>patient<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
If you are setting the patient in the view, then just leave the patient form out of the list of fields for you model form:
DiagnosisForm(forms.ModelForm):
class Meta:
model = Diagnosis
fields = ('myfield1', 'myfield2', ...)
You can use exclude if you prefer:
DiagnosisForm(forms.ModelForm):
class Meta:
model = Diagnosis
exclude = ('author', 'patient',)
The problem in your current form is that you have
exclude = ['patient']
exclude = ('author',)
The second exclude replaces the first. You should have:
exclude = ['author', 'patient']
See the model form docs on selecting the fields to use for more info.

Django form insert record instead of update record

I am having some issues trying to update some records in Django:
When i try to update some record, the app insert a new one, I don't know why i have this behavior.
Model
class DetalleRecepcion(models.Model):
id_proveedor = models.ForeignKey(Proveedor,db_column='id_proveedor',primary_key=True, verbose_name='Proveedor')
anio = models.IntegerField( null=False)
mes = models.IntegerField(verbose_name='Mes')
fecha_recepcion = models.DateField(verbose_name='Fecha Recepcion')
usuario = models.CharField(max_length=15, blank=True)
num_archivos = models.IntegerField(primary_key=True, verbose_name='No de archivos')
class Meta:
managed = False
db_table = 'mpc_detalle_recepcion'
view:
#login_required(login_url='/login/')
def DetRecView(request):
idp = request.GET.get('i')
anio = request.GET.get('a')
mes = request.GET.get('m')
if request.method == 'POST':
r = DetalleRecepcion.objects.get(id_proveedor=idp,anio=anio,mes=mes)
form = DetRecForm(request.POST or None, instance =r)
if form.is_valid():
form.save()
return HttpResponse('<script type="text/javascript">window.close()</script>')
else:
r = DetalleRecepcion.objects.get(id_proveedor=idp,anio=anio,mes=mes)
r.usuario = request.user
form = DetRecForm(instance=r)
return render_to_response('detrec.html',
{'form':form},
context_instance=RequestContext(request))
Form:
class DetRecForm(forms.ModelForm):
fecha_recepcion = forms.DateField(widget=DateInput(),)
def __init__(self,*args,**kwargs):
super(DetRecForm,self).__init__(*args,**kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field('id_proveedor',
'anio',
'mes',
'usuario',
readonly = True
),
Fieldset('',
'fecha_recepcion',
'num_archivos',
Submit('save','Grabar'),
HTML('<a class="btn btn-danger" id="cerrar">Cancelar</a>')
)
)
class Meta:
model = DetalleRecepcion
I use the same view and form definition for others models to render edit forms and with this other models works great and the records are updated.
I don't understand what it's happen.
I rewrite the form, view definition for this model and I don't know what it is the problem.
The database is a legacy database and the tables doesn't have any kind of relationship or constraint.
By the way I am using Django crispy form
Thanks in advance
If you using same form for create and update views, then you need provide clean method on your unique field and raise ValidationError when object exists.
But in your case, I assuming you using Composite Primary Key on fields: id_proveedor, num_archivos, you should override clean method of the whole form:
class DetRecForm(forms.ModelForm):
fecha_recepcion = forms.DateField(widget=DateInput())
def __init__(self, *args, **kwargs):
super(DetRecForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field('id_proveedor',
'anio',
'mes',
'usuario',
readonly=True
),
Fieldset('',
'fecha_recepcion',
'num_archivos',
Submit('save', 'Grabar'),
HTML('<a class="btn btn-danger" id="cerrar">Cancelar</a>')
)
)
def clean(self):
cleaned_data = super(DetRecForm, self).clean()
id_proveedor = self.cleaned_data['id_proveedor']
num_archivos = self.cleaned_data['num_archivos']
qs = self.Meta.model.objects.filter(id_proveedor=id_proveedor, num_archivos=num_archivos)
if self.instance:
qs = qs.exclude(pk=self.instance.id)
if qs.count() > 0:
raise forms.ValidationError(u'Such object exists!')
return cleaned_data
class Meta:
model = DetalleRecepcion
Try to get object by pk for instance
DetalleRecepcion.objects.get(pk=kwargs['pk'])

Categories