Django: 'UNIQUE constraint failed' - django

I have override init in RandomisationFOrm and have an error I did not have before
I understand the error: in my models Randomization ran_num field is UNIQUE
and when I try to save a form when I call randomisation_edit view with the same ran_num it raise error
but before overriding __init__, this error was manage by Django itself
I try to add validation as below but raise another error: 'RandomisationForm' object has no attribute 'clean_data' -> I do the same for other field without this error...
def clean_ran_num(self):
data = self.clean_data['ran_num']
if data == 'CIV-CO-001':
raise forms.validationError('test')
return data
I try to add validation in the global 'clean' method but it do not apply...
I do not understand...
models.py
class Randomisation(models.Model):
# ran_num = models.CharField("Patient code", max_length=10, unique=True, null=True, blank=True,validators=[validate_patient_code])
ran_ide = models.AutoField(primary_key=True)
ran_num = models.CharField("Patient code", max_length=10, unique=True, null=True, blank=True)
ran_dat = models.DateField("Date of randomization", null=True, blank=True)
ran_inv = models.CharField("Investigator", max_length=20, null=True, blank=True)
ran_pro = models.IntegerField("Procedure used to randomized", null=True, blank=True)
ran_pro_per = models.CharField("if telephone, name of the person reached on the phone", max_length=20, null=True, blank=True)
ran_crf_inc = models.IntegerField("Was the CRF inclusion 2 Eligibility fulfilled?", null=True, blank=True)
ran_tbc = models.IntegerField("Is TB diagnosis possible, probable or definite?", null=True, blank=True)
ran_crf_eli = models.IntegerField("Was the CRF Inclusion 2 Eligibility fulffiled?", null=True, blank=True)
ran_cri = models.IntegerField("Is the conclusion All criteria fulfilled for randomisation", null=True, blank=True)
ran_sta = models.IntegerField("British Medical Council Staging (worst stage reported between first symptoms and now)", null=True, blank=True)
ran_vih = models.IntegerField("HIV/AIDS status", null=True, blank=True)
ran_bra = models.IntegerField("TB treatment assigned", null=True, blank=True)
ran_med = models.CharField("Batch number", max_length=6, null=True, blank=True)
ran_log_dat = models.DateTimeField("Date", null=True, blank=True)
ran_log = models.CharField("Name of the person who performs randomisation on site", max_length=10, null=True, blank=True)
forms.py
class RandomisationForm(forms.ModelForm):
# surcharge méthode constructeur (__init__) pour avoir accès aux variables de sessions
# https://stackoverflow.com/questions/3778148/django-form-validation-including-the-use-of-session-data
def __init__(self, request, *args, **kwargs):
self.request = request
super(RandomisationForm, self).__init__(*args, **kwargs)
# vérification que le numéro patient saisi correspond bien au site actuellement sélectionné
# i.e. profil 'International'
def clean(self):
data = self.cleaned_data['ran_num']
if data[4:6] != self.request.session.get('selected_site'):
raise ValidationError(_('Patient code is different from the selected site'))
TYPES = [
(None, ''),
(1, _('On-line')),
(2, _('Telephon')),
]
YESNO = [
(None, ''),
(1, _('Yes')),
(0, _('No')),
]
TB = [
(None, ''),
(1, _('Possible')),
(2, _('Probable')),
(3, _('Definite')),
]
SEVERITY = [
(None, ''),
(1, _('Mild')),
(2, _('Severe')),
]
HIV = [
(None, ''),
(0, _('Negative')),
(1, _('Positive')),
]
ran_dat = forms.DateField(
label = _("Date of randomization"),
initial = datetime.datetime.now,
required = True,
)
ran_num = forms.CharField(label = _("Patient number"), required=True) # ajout
ran_inv = forms.CharField(label = _("Investigator"), required=True)
ran_pro = forms.ChoiceField(label = _("Type"), widget=forms.Select, choices=TYPES)
ran_pro_per = forms.CharField(label = _("if telephone, name of the person reached on the phone"), required=False)
ran_crf_inc = forms.ChoiceField(label = _("Was the CRF inclusion 1 TB diagnosis fulfilled?"), widget = forms.Select, choices = YESNO)
ran_crf_eli = forms.ChoiceField(label = _("Was the CRF Inclusion 2 Eligibility fulffiled?"), widget = forms.Select, choices = YESNO)
ran_tbc = forms.ChoiceField(label = _("Is TB diagnosis possible, probable or definite?"), widget = forms.Select, choices = TB)
ran_cri = forms.ChoiceField(label = _("Is the conclusion All criteria fulfilled for randomisation"), widget=forms.Select, choices = YESNO)
ran_sta = forms.ChoiceField(label = _("British Medical Council Staging (worst stage reported between first symptoms and now)"), widget = forms.Select, choices = SEVERITY)
ran_vih = forms.ChoiceField(label = _("HIV/AIDS status"), widget = forms.Select, choices = HIV)
class Meta:
model = Randomisation
# Tous les champs sauf les champs de log et les champs TB treatment et Drug batch number
fields = ('ran_num','ran_dat','ran_inv','ran_pro','ran_pro_per','ran_crf_inc','ran_tbc','ran_crf_eli','ran_cri','ran_sta','ran_vih',)
def clean_ran_crf_inc(self):
data = self.cleaned_data['ran_crf_inc']
if int(data) == 0:
raise forms.ValidationError(_("This criteria is mandatory for randomization"))
return data
def clean_ran_crf_eli(self):
data = self.cleaned_data['ran_crf_eli']
if int(data) == 0:
raise forms.ValidationError(_("This criteria is mandatory for randomization"))
return data
def clean_ran_cri(self):
data = self.cleaned_data['ran_cri']
if int(data) == 0:
raise forms.ValidationError_(("This criteria is mandatory for randomization"))
return data
views.py
def randomisation_edit(request):
if request.method == "POST":
form = RandomisationForm(request, data=request.POST or None)
if form.is_valid():
randomisation = form.save()
# randomisation
rand = Randomiser(randomisation.ran_num, randomisation.ran_vih, randomisation.ran_sta)
# Mise à jour de la base de données -> Patient code pour rendre indisponible la ligne
# pour une prochaine randomisation
# ListeRandomisation
# 1.Récupération de la ligne dans la liste de rando
bras = rand['bras']
# 2.Mise à jour de la ligne dans la liste de rando
patient = ListeRandomisation.objects.get(ran_ide = bras)
patient.ran_dis = randomisation.ran_num # pat
patient.save()
# Medicament
# 1.Récupération de la ligne dans la liste de correspondance médicament
medicament = rand['medicament']
# 2.Mise à jour de la ligne dans la liste de correspondance médicament
medicament = Medicament.objects.get(med_ide = medicament)
medicament.ran_dis = randomisation.ran_num # pat
medicament.save()
# mise à jour de la variable de session can_randomize
request.session['can_randomize'] = is_randomizable(medicament.ran_dis)
# Mise à jour de la table Randomisation avec le bras et le numéro de boite attribués
randomisation.ran_bra = patient.ran_bra
randomisation.ran_med = medicament.med_num
randomisation.ran_log_dat = datetime.now()
randomisation.ran_log = request.user.username
randomisation.save()
return redirect('randomization:confirmation', pk = randomisation.pk)
else:
form = RandomisationForm(request)
return render(request, 'randomization/randomisation_edit.html', {'form': form})

When you override the Form.clean method you have to call super(RandomisationForm, self).clean() or you will loose the unique constraint checks.
From the docs:
https://docs.djangoproject.com/en/3.0/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
The call to super().clean() in the example code ensures that any validation logic in parent classes is maintained. If your form inherits another that doesn’t return a cleaned_data dictionary in its clean() method (doing so is optional), then don’t assign cleaned_data to the result of the super() call and use self.cleaned_data instead:

Related

Django: field validator and UNIQUE constraint are not applyed

I have a Randomisation model with pat field that should be unique and have a specified format.
I defined unique=true and and validators but neither are applyed
For example, FRA-0001 sould be valid and if I try to registered this code twice it should raise an error validation message on my form (but not crash database).
But currently, I can registered FRA for example and if I registered it twice I raise database error UNIQUE contraint failed
models.py
class Randomisation(models.Model):
...
pat = models.CharField("Patient number", max_length=8, unique=True, null=True, blank=True,
validators = [
RegexValidator(
regex='^[A-Z]{3}-[0-9]{4}$',
message= 'L\'identifiant doit être au format XXX-0000',
code='invalid_participant_id'
),
],
)
...
forms.py
class RandomizationEditForm(forms.Form):
def __init__(self, request, *args, **kwargs):
super(RandomizationEditForm, self).__init__(*args, **kwargs)
self.user_country = Pays.objects.get(pay_ide = request.session.get('user_country'))
# self.language = request.session.get('language')
# print(self.language)
self.user_site_type = request.session.get('user_site_type')
PAYS = Pays.options_list(self.user_country,self.user_site_type,'fr')
SEXE = Thesaurus.options_list(2,'fr')
STRATE_1 = Thesaurus.options_list(3,'fr')
STRATE_2 = Thesaurus.options_list(4,'fr')
YES = [(None,''),(0,'Non'),(1,'Oui'),]
self.fields["pat"] = forms.CharField(label = "Numéro patient (XXX-0000)")
self.fields['pat'].widget.attrs.update({
'autocomplete': 'off'
})
self.fields["ran_nai"] = forms.IntegerField(label = "Date de naissance (année)", widget=forms.TextInput)
self.fields['ran_nai'].widget.attrs.update({
'autocomplete': 'off'
})
self.fields["ran_sex"] = forms.ChoiceField(label = "Sexe", widget=forms.Select, choices=SEXE)
self.fields["ran_st1"] = forms.ChoiceField(label = "Gravité de la maladie COVID-19", widget=forms.Select, choices=STRATE_1)
self.fields["ran_bug"] = forms.BooleanField(label = "Recours à la procédure de secours ?", required = False)
self.fields["ran_dem_nom"] = forms.CharField(label = "Nom de la personne qui demande la randomisation", required = False, initial=None)
self.fields['ran_dem_nom'].widget.attrs.update({
'autocomplete': 'off'
})
self.fields["ran_dem_dat"] = forms.DateField(
# input_formats=settings.DATE_INPUT_FORMATS,
label = "Date de la demande",
initial = timezone.now(),
required = False,
)
self.fields['ran_dem_dat'].widget.attrs.update({
'autocomplete': 'off'
})
# def clean_pat(self):
# data = self.cleaned_data['pat']
# if len(data) < 8:
# raise forms.ValidationError("Merci de contrôler la numéro patient")
# return data
def clean_ran_nai(self):
data = self.cleaned_data['ran_nai']
if data > 2003:
raise forms.ValidationError("Merci de contrôler la date de naissance")
return data
def clean_ran_dem_dat(self):
data = self.cleaned_data['ran_dem_dat']
entrydate = datetime.datetime.strptime(str(data), "%Y-%m-%d")
currentdate = datetime.datetime.now()
if entrydate > currentdate:
raise forms.ValidationError("Merci de contrôler la date (postérieure à la date du jour)")
return data
views.py
def randomization_edit(request):
if request.method == "POST":
form = RandomizationEditForm(request, data=request.POST or None)
if form.is_valid():
# Récupération des données permettant la randomisation
randomisation = Randomisation.objects.filter(Q(pay_ide=form.data.get('pay_ide')) & Q(ran_act=1) & Q(ran_st1=form.data.get('ran_st1')) & Q(pat=None)).first()
randomisation.pat = form.cleaned_data['pat']
randomisation.ran_nai = form.data.get('ran_nai')
randomisation.ran_sex = form.data.get('ran_sex')
randomisation.ran_bug = form.cleaned_data['ran_bug']
if form.data.get('ran_dem_nom') == '':
randomisation.ran_dem_nom = None
else:
randomisation.ran_dem_dat = form.data.get('ran_dem_dat')
randomisation.ran_log = request.user.username
randomisation.ran_dat = timezone.now()
randomisation.save()
return redirect('randomization:confirmation', pk = randomisation.pk)
else:
form = RandomizationEditForm(request)
return render(request, 'randomization/edit.html', {'form': form})
You're redefining the pat field as a CharField in your form, so none of the validations you specified on the model apply. You can either rewrite your form to be a ModelForm or add the regex and uniqueness validation into your form.
class RandomizationEditForm(forms.Form):
def __init__(self, *args, **kwargs):
super(RandomizationEditForm, self).__init__(*args, **kwargs)
self.fields["pat"] = forms.CharField(label = "Numéro patient (XXX-0000)", validators=[RegexValidator(
regex='^[A-Z]{3}-[0-9]{4}$',
message= 'L\'identifiant doit être au format XXX-0000',
code='invalid_participant_id'
)])
def clean_pat(self):
data = self.cleaned_data['pat']
if Randomisation.objects.filter(pat=data).exists():
raise forms.ValidationError("L\'identifiant doit être unique")
return data

Django: BooleanField return 'on' instead of true?

I have a BooleanField (ran_bug) in my Randomisation models that is displayed as a checkbox.
Click on the checkbox sould show 2 other fields that are not mandatory (ran_dem_nom and ran_dem_dat).
My problem is that, when I 'check' the checkbox, it return 'on' instead of true.
And I got an error when I try to registered data:
django.core.exceptions.ValidationError: ["'on' value must be either True, False, or None."]
models.py
class Randomisation(models.Model):
ran_ide = models.AutoField(primary_key=True)
pay_ide = models.ForeignKey(Pays, on_delete = models.CASCADE) # related country
ran_str_num = models.CharField("Logical numerotation", max_length=2, null=True, blank=True)
ran_bra = models.CharField("Arm", max_length=1, null=True, blank=True)
bra_lib = models.CharField("Arm label", max_length=50, null=True, blank=True)
ran_act = models.IntegerField("Activated line", null=True, blank=True)
pat = models.CharField("Patient number", max_length=12, unique=True, null=True, blank=True)
ran_nai = models.IntegerField("Patient birthdate (year)", blank=True)
ran_sex = models.IntegerField("Sex", null=True, blank=True)
ran_st1 = models.IntegerField("Stratification variable 1", blank=True)
ran_st2 = models.IntegerField("Stratification variable 2", blank=True)
ran_bug = models.BooleanField("Use of alternative randomization procedure?", null=True, blank=True)
ran_dem_nom = models.CharField("Name of the person asking for randomization", max_length=12, null=True, blank=True) # hide at pageload
ran_dem_dat = models.DateField("Date of demand", null=True, blank=True) # hide at pageload
ran_log = models.CharField("User", max_length=12, null=True, blank=True)
ran_dat = models.DateTimeField("Date", null=True, auto_now_add=True, blank=True)
forms.py
class RandomizationEditForm(forms.Form):
def __init__(self, request, *args, **kwargs):
super(RandomizationEditForm, self).__init__(*args, **kwargs)
self.user_country = Pays.objects.get(pay_ide = request.session.get('user_country'))
self.user_site_type = request.session.get('user_site_type')
PAYS = Pays.options_list(self.user_country,self.user_site_type,'fr')
SEXE = Thesaurus.options_list(2,'fr')
STRATE_1 = Thesaurus.options_list(3,'fr')
STRATE_2 = Thesaurus.options_list(4,'fr')
YES = [(None,''),(0,'Non'),(1,'Oui'),]
self.fields["pay_ide"] = forms.IntegerField(label = "Pays", initial=2, widget=forms.HiddenInput())
self.fields["pat"] = forms.CharField(label = "Numéro patient (XXX-0000)")
self.fields['pat'].widget.attrs.update({
'autocomplete': 'off'
})
self.fields["ran_nai"] = forms.IntegerField(label = "Date de naissance (année)", widget=forms.TextInput)
self.fields['ran_nai'].widget.attrs.update({
'autocomplete': 'off'
})
self.fields["ran_sex"] = forms.ChoiceField(label = "Sexe", widget=forms.Select, choices=SEXE)
self.fields["ran_st1"] = forms.ChoiceField(label = "Gravité de la maladie COVID-19", widget=forms.Select, choices=STRATE_1)
self.fields["ran_bug"] = forms.BooleanField(label = "Recours à la procédure de secours ?", required = False)
self.fields["ran_dem_nom"] = forms.CharField(label = "Nom de la personne qui demande la randomisation", required = False)
self.fields['ran_dem_nom'].widget.attrs.update({
'autocomplete': 'off'
})
self.fields["ran_dem_dat"] = forms.DateField(
# input_formats=settings.DATE_INPUT_FORMATS,
label = "Date de la demande",
initial = timezone.now(),
required = False,
)
self.fields['ran_dem_dat'].widget.attrs.update({
'autocomplete': 'off'
})
JS
$(function(){
$("#div_id_ran_dem_nom").hide();
$("#div_id_ran_dem_dat").hide();
});
// affichage des champs en fonction de la valeur sélectionnée dans la liste
$("#div_id_ran_bug").on("change", function(event){
console.log($("#id_ran_bug").val())
if ($("#id_ran_bug").is(":checked")){
$("#div_id_ran_dem_nom").show();
$("#div_id_ran_dem_dat").show();
}
else {
$("#div_id_ran_dem_nom").hide();
$("#div_id_ran_dem_dat").hide();
}
});
views.py
def randomization_edit(request):
if request.method == "POST":
form = RandomizationEditForm(request, data=request.POST or None)
if form.is_valid():
# Récupération des données permettant la randomisation
randomisation = Randomisation.objects.filter(Q(pay_ide=form.data.get('pay_ide')) & Q(ran_act=1) & Q(ran_st1=form.data.get('ran_st1')) & Q(pat=None)).first()
randomisation.pat = form.data.get('pat')
randomisation.ran_nai = form.data.get('ran_nai')
randomisation.ran_sex = form.data.get('ran_sex')
randomisation.ran_bug = form.data.get('ran_bug')
randomisation.ran_dem_nom = form.data.get('ran_dem_nom')
randomisation.ran_dem_dat = form.data.get('ran_dem_dat')
print('ran_bug',form.data.get('ran_bug'))
randomisation.ran_log = request.user.username
randomisation.ran_dat = timezone.now()
randomisation.save()
return redirect('randomization:confirmation', pk = randomisation.pk)
else:
form = RandomizationEditForm(request)
return render(request, 'randomization/edit.html', {'form': form})
OK, i resolve my problem: form.cleaned_data['ran_bug'] instead of form.data.get('ran_bug')

Django Rest Framework Post nested models

I have three related models and I need to save all instances in a single transaction.
A simplified version of my models are:
class Recibosventas(models.Model):
idreciboventa = models.AutoField(primary_key=True)
idemplea = models.ForeignKey(Empleados, models.DO_NOTHING, db_column='idemplea', blank=True, null=True)
idempres = models.SmallIntegerField()
categocompro = models.CharField(max_length=2)
tipocompro = models.CharField(max_length=1)
puntoventa = models.SmallIntegerField()
numero = models.IntegerField(blank=True, null=True)
class Meta:
managed = True
db_table = 'recibosventas'
unique_together = (('idempres', 'categocompro', 'tipocompro', 'puntoventa', 'numero'),)
class Fondos(models.Model):
idfondo = models.AutoField(primary_key=True)
idemplea = models.ForeignKey(Empleados, models.DO_NOTHING, db_column='idemplea', blank=True, null=True)
idtipofondo = models.ForeignKey('Tiposfondo', models.DO_NOTHING, db_column='idtipofondo')
idconcepfondo = models.ForeignKey(Conceptosfondos, models.DO_NOTHING, db_column='idconcepfondo')
idreciboventa = models.ForeignKey('Recibosventas', models.DO_NOTHING, db_column='idreciboventa', related_name='fondoreciboventa', blank=True, null=True)
class Meta:
managed = True
db_table = 'fondos'
class Movimientosfondos(models.Model):
idmovimifondo = models.AutoField(primary_key=True)
idfondo = models.ForeignKey(Fondos, models.DO_NOTHING, db_column='idfondo', related_name='movimientosfondos', blank=True, null=True)
iddispon = models.ForeignKey(Disponibilidades, models.DO_NOTHING, db_column='iddispon')
class Meta:
managed = True
db_table = 'movimientosfondos'
serializers.py:
class MovimientosfondosSerializer(serializers.ModelSerializer):
class Meta:
model = Movimientosfondos
fields='__all__'
class FondosSerializer(serializers.ModelSerializer):
movimientosfondos = MovimientosfondosSerializer(many=True)
class Meta:
model = Fondos
fields='__all__'
def create(self, validated_data):
with transaction.atomic():
# Crear la instancia de pedidos de venta
movimientosfondos_data = validated_data.pop('movimientosfondos')
fondos = Fondos.objects.create(**validated_data)
# crear cada instancia de movimientosfondos
for movimientosfondos_data in movimientosfondos_data:
movimientosfondos_data['idfondo'] = fondos
Movimientosfondos.objects.create(**movimientosfondos_data)
return fondos
def update(self, instance, validated_data):
with transaction.atomic():
# Crear la instancia de pedidos de venta
movimientosfondos_data = validated_data.pop('movimientosfondos')
# Eliminar el detalle de ventas
for movimientosfondos in (instance.movimientosfondos).all():
movimientosfondos.delete()
# crear nuevamente cada instancia de ventasarticulos
for movimientosfondos_data in movimientosfondos_data:
movimientosfondos_data['idfondo'] = instance
Movimientosfondos.objects.create(**movimientosfondos_data)
return instance
class RecibosventasSerializer(serializers.ModelSerializer):
fondoreciboventa = FondosSerializer(many=True)
class Meta:
model = Recibosventas
fields='__all__'
def create(self, validated_data):
with transaction.atomic():
# Crear la instancia de pedidos de venta
fondosreciboventa_data = validated_data.pop('fondoreciboventa')
recibosventas = Recibosventas.objects.create(**validated_data)
# crear cada instancia de fondos
for fondoreciboventa_data in fondosreciboventa_data:
fondoreciboventa_data['idreciboventa'] = recibosventas
Fondos.objects.create(**fondoreciboventa_data)
return recibosventas
def update(self, instance, validated_data):
with transaction.atomic():
# Crear la instancia de pedidos de venta
fondosreciboventa_data = validated_data.pop('fondoreciboventa')
for fondoreciboventa in (instance.fondoreciboventa).all():
fondoreciboventa.delete()
# crear nuevamente cada instancia de pedidoventasarticulos
for fondoreciboventa_data in fondosreciboventa_data:
fondoreciboventa_data['idreciboventa'] = instance
Fondos.objects.create(**fondoreciboventa_data)
return instance
apiviews:
class FondosList(generics.ListCreateAPIView):
queryset = Fondos.objects.all()
serializer_class = FondosSerializer
def perform_create(self, serializer):
idemplea = Empleados.objects.get(usuariweb = self.request.user)
serializer.save(idemplea=idemplea)
class RecibosventasList(generics.ListCreateAPIView):
queryset = Recibosventas.objects.all()
serializer_class = RecibosventasSerializer
def perform_create(self, serializer):
idemplea = Empleados.objects.get(usuariweb = self.request.user)
serializer.save(idemplea=idemplea)
class RecibosventasDetalle(generics.RetrieveDestroyAPIView):
queryset = Recibosventas.objects.all()
serializer_class = RecibosventasSerializer
def perform_destroy(self, instance):
with transaction.atomic():
# Eliminar el detalle de pedidosventas
for movimientosfondos in (instance.fondoreciboventa.movimientosfondos).all():
movimientosfondos.delete()
instance.fondoreciboventa.delete()
# Eliminar el encabezado pedidosventas
instance.delete()
class RecibosventasUpdate(generics.UpdateAPIView):
queryset = Recibosventas.objects.all()
serializer_class = RecibosventasSerializer
urls:
urlpatterns = [
path('v1/recibosventas/', RecibosventasList.as_view(), name='recibosventas_list'),
path('v1/recibosventas/<int:pk>', RecibosventasDetalle.as_view(), name='recibosventas_detalle'),
path('v1/recibosventasupdate/<int:pk>', RecibosventasUpdate.as_view(), name='recibosventas_update'),
]
I send to the api:
{
"idreciboventa": null,
"idemplea" : 1,
"fondoreciboventa": [
{
"idfondo": 1,
"idemplea" : 1,
"movimientosfondos": [
{
"idmovimifondo": 1,
"idfondo": 1,
"iddispon": 9,
}
],
"idtipofondo": 1,
"idconcepfondo": 2,
}
]
"categocompro": "RC",
"tipocompro" : "F",
"puntoventa": 1,
"numero" : 1,
}
I get the following error:
Direct assignment to the reverse side of a related set is prohibited. Use movimientosfondos.set() instead.
I don't understand what I'm doing wrong. Somebody could help me please?

Reusable views method for multiple model classes Django

Guys I want to create a tools inventory app.
I have this method that will do the exact same thing on all tools types. What I want to know is how can I use this method dynamically selecting what models to use (maybe how to pass a model class as an argument).
It's currently working only for one type of tool. My models.py has an abstract model that has all common fields for all tools and then various models that inherit it for tool specific fields (e.i. End mills, Drill-bits, pliers, screwdrivers, etc all inherit common filed from my abstract model).
def calcular_nueva_cantidad(ce, up, get_link):
if get_link == 'incremento':
total = ce + up
else:
total = -(ce - up)
return total
def calcular_nuevo_total(nce, pu):
total = nce * pu
return total
# crea el formulario para la actualizacion de cantidad existente
class updateForm(forms.Form):
update = forms.IntegerField()
def actualizar_cantidad(request, pk, model ):
# trae de la base de datos el valor de la cantidad_existente
cantidad_existente = model.objects.filter(pk=pk).values('cantidad_existente')
c = cantidad_existente.values_list('cantidad_existente', flat=True)
ce = c[0]
# trae de la base de datos el valor de la precio_unitario
precio_unitario = model.objects.filter(pk=pk).values('precio_unitario')
p = precio_unitario.values_list('precio_unitario', flat=True)
pu = p[0]
# trae de la base de datos el valor de la total
qs_total = model.objects.filter(pk=pk).values('total')
if request.method =='POST':
form = updateForm(request.POST)
if form.is_valid():
# Obtiene el name de urls para el link segun sea el caso
get_link = resolve(request.path_info).url_name
get_linkwargs = resolve(request.path_info).kwargs
print(F'========>{get_link, get_linkwargs }<=========')
up = form.cleaned_data['update']
# Calcula el nuevo valor de cantidad existente
nce = calcular_nueva_cantidad(up, ce, get_link)
# Actualiza la nueva cantidad existente
cantidad_existente.update(cantidad_existente=nce)
# Calcula el nuevo valor de cantidad existente
s_total = calcular_nuevo_total(nce, pu)
# Actualiza la nueva cantidad existente
qs_total.update(total=s_total)
# Obteiene item id del tipo de cortador asi puede regresar a la pantalla del listado
pp = model.objects.filter(pk=pk).values('tipo')
ppp = pp.values_list('tipo', flat=True)
pk = ppp[0]
return HttpResponseRedirect(reverse('inventario:cortadores-list', args=(pk, )))
else:
# Redirect to fail page after POST
return HttpResponse('')
else:
form = updateForm()
return render(request, 'inventario/update.html', {'form':form})
This is my abstract class in my models.py
class Item(models.Model):
description = models.CharField(max_length=30,)
numero_parte = models.CharField(max_length=30)
proveedor = models.ForeignKey(Proveedor, on_delete=models.CASCADE)
cantidad_existente = models.PositiveIntegerField()
update = models.PositiveIntegerField(blank=True, default=0)
cantidad_minima = models.PositiveIntegerField()
precio_unitario = models.DecimalField(max_digits=7, decimal_places=2)
total = models.DecimalField(max_digits=7, decimal_places=2, blank=True)
asignado_a = models.ForeignKey(Empleados, on_delete=models.CASCADE, blank=True, null=True)
anaquel = models.CharField(max_length=2, choices=ANAQUEL, blank=True, null=True)
posicion_en_x = models.CharField(max_length=2, blank=True, null=True)
posicion_en_y = models.CharField(max_length=2, blank=True, null=True)
activo = models.BooleanField()
class Meta:
abstract = True
def save(self,*args,**kwargs):
self.total = self.cantidad_existente * self.precio_unitario
super().save(*args,**kwargs)
These two classes inherit from Item
class Cortadores(Item):
tipo = models.ForeignKey(Tipos_Cortadores,on_delete=models.CASCADE)
material = models.ForeignKey(Materiales, on_delete=models.CASCADE)
filos = models.CharField(max_length=5, choices=GABILANES)
diametro = models.ForeignKey(Diametros, on_delete=models.CASCADE)
longitud = models.ForeignKey(Longitud, on_delete=models.CASCADE)
desbaste = models.CharField(max_length=1, choices=DESBASTE)
class Meta:
verbose_name_plural = "Cortadores"
def get_absolute_url(self):
return reverse('inventario:cortadores-list', kwargs={'id': self.tipo.id})
def __str__(self):
return '%s %s %s %s %s %s' % ( str(self.tipo), str(self.material), str(self.filos), str(self.diametro),
self.longitud, self.desbaste
)
class Tornillos(Item):
tipo = models.CharField(max_length=10, choices=TIPO_TORNILLO)
paso = models.ForeignKey(Paso_Tornillo, on_delete=models.CASCADE)
material = models.ForeignKey(Materiales, on_delete=models.CASCADE)
longitud = models.ForeignKey(Longitud, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "Tornillos"
def get_absolute_url(self):
return reverse('inventario:lista-herramientas-mecanicas')#, kwargs={'pk': self.pk})
def __str__(self):
return '%s %s %s %s' % (str(self.tipo), str(self.paso), str(self.material), str(self.longitud))
I made a class for every tool type that I own. I only included these two classes for simplicity and not fill the post with a lot of code.
If you still can change the design of your tables, you could use multi-table-inheritance instead of the abstract-base-class you are using now.
That would mean that you will have one model Item (which will be an actual database table) and then other models that inherit from this model (their database tables will only hold the new fields, but not repeat any fields).
For that change you only need to remove abstract = True from Item.Meta; you should not need to change other parts form your models. Of course, you will have to drop your tables and run migrations again (will destroy your current data).
As I said, this is only an option if you can still change your database tables significantly.
With this setup, you can query your Item model directly for the common fields. There are some subtleties you have to be careful about, but read the docs I linked to see if it solves your issue.

Django - How can i assign a value to a object using a formset but in a view

(Sorry for my bad english)
I need to add a value to a object field, that i'm excluding from the formset. i like to auto assign this in a view. (i can't modify the model to add a def save method and make it there because is a third party app model)
This is the model
class Tax(models.Model):
"""A tax (type+amount) for a specific Receipt."""
tax_type = models.ForeignKey(
TaxType,
verbose_name=_('tax type'),
on_delete=models.PROTECT,
)
description = models.CharField(
_('description'),
max_length=80,
)
base_amount = models.DecimalField(
_('base amount'),
max_digits=15,
decimal_places=2,
)
aliquot = models.DecimalField(
_('aliquot'),
max_digits=5,
decimal_places=2,
)
amount = models.DecimalField(
_('amount'),
max_digits=15,
decimal_places=2,
)
receipt = models.ForeignKey(
Receipt,
related_name='taxes',
on_delete=models.PROTECT,
)
def compute_amount(self):
"""Auto-assign and return the total amount for this tax."""
self.amount = self.base_amount * self.aliquot / 100
return self.amount
class Meta:
verbose_name = _('tax')
verbose_name_plural = _('taxes')
This is the form and formset
class TaxForm(forms.ModelForm):
class Meta:
model = Tax
fields = [
'tax_type',
'description',
'base_amount',
'aliquot',
]
ReceiptTaxFormset = inlineformset_factory(
Receipt,
Tax,
form=TaxForm,
extra=0,
can_delete=False,
)
And this is the part of the view where i handle the formset
if form.is_valid() and entryFormset.is_valid() and taxFormset.is_valid():
receipt = form.save(commit=False)
# Tomamos el punto de venta de la sucursal y lo asignamos
pos = request.user.userprofile.branch_office.point_of_sales
receipt.point_of_sales = pos
receipt.document_number = client.dni_cuit
# Controlamos si el dni o cuit tiene 11 caracteres
# Si los tiene asigna CUIT al típo de documento
if len(client.dni_cuit) == 11:
document_type = DocumentType.objects.get(id=1)
receipt.document_type = document_type
else:
document_type = DocumentType.objects.get(id=10)
receipt.document_type = document_type
# Tomamos los valores de las lineas del comprobante
# y generamos los totales para asentar en el comprobante
total_amount = 0
for f in entryFormset:
cd = f.cleaned_data
qty = cd.get('quantity')
price = cd.get('unit_price')
vat = cd.get('vat')
subtotal = qty * price
total_amount = total_amount + subtotal
for t in taxFormset:
cd = t.cleaned_data
ba = cd.get('base_amount')
al = cd.get('aliquot')
ta = ba * al / 100
total_amount = total_amount + ta
# Asignamos el monto total a total_ammount
# y a net_taxed para factura tipo C ya que es igual
receipt.total_amount = total_amount
receipt.net_taxed = total_amount
# Asignamos 0 para Factura tipo C a los campos no necesarios
receipt.net_untaxed = 0
receipt.exempt_amount = 0
# Guardamos el comprobante y las lineas del mismo
receipt.save()
entryFormset.save()
taxFormset.save()
What i need to do is that in the taxFormset, for each tax that i get from the form, assign the amount to the object ta = ba * al / 100
Thanks!
inlineformset_factory sits on top of modelformset_factory which uses instances
I would love to see what error it's giving you or if it's not saving but maybe you are missing on something like this.
receipt.instance.total_amount = total_amount
receipt.instance.net_taxed = total_amount
# Asignamos 0 para Factura tipo C a los campos no necesarios
receipt.instance.net_untaxed = 0
receipt.instance.exempt_amount = 0
# Guardamos el comprobante y las lineas del mismo
receipt.save()
entryFormset.save()
taxFormset.save()