How to save a nested relationship in django rest framework? - python

I am trying to save a nested relationship in Django REST Framework following the Django REST Framework guide but I couldn't do that.
I have a class "Asiento" which is a foregin key for another class "Apunte". When I get every "Asiento" Django REST Framework returns them and all of their "Apunte" objects. This works, but when I try to create/update an "Asiento" with writable nested serializers I get that AsientoSerializer(data=data).is_valid() == False.
My models:
class Apunte(TxerpadBase):
debe = models.DecimalField(null=True, blank=True, max_digits=18, decimal_places=6)
haber = models.DecimalField(null=True, blank=True, max_digits=18, decimal_places=6)
cuenta = models.ForeignKey(Cuenta, related_name='mayor', on_delete=models.PROTECT)
partner = models.ForeignKey(Partner, null=True, blank=True, on_delete=models.PROTECT)
asiento = models.ForeignKey(Asiento, related_name='apuntes')
fecha = models.DateField()
recc = models.BooleanField(blank=True, default=False)
conciliacion = models.ForeignKey('Conciliacion', null=True, blank=True, on_delete=models.SET_NULL)
estado = FSMField(default='borrador')
class Asiento(TxerpadBase):
numero = models.PositiveIntegerField(null=True, blank=True)
fecha = models.DateField(blank=True, default=datetime.datetime.now)
libro = models.ForeignKey('Libro', on_delete=models.PROTECT)
periodo = models.ForeignKey(Periodo, on_delete=models.PROTECT)
estado = FSMField(default='borrador')
My serializers:
class ApunteSerializer(serializers.ModelSerializer):
fecha = serializers.DateField(
format='%d-%m-%Y', input_formats=('%d-%m-%Y',),
error_messages={'invalid': 'La fecha del apunte no esta en el formato correcto.'}
)
class Meta:
model = Apunte
class AsientoSerializer(serializers.ModelSerializer):
fecha = serializers.DateField(
format='%d-%m-%Y', input_formats=('%d-%m-%Y',),
error_messages={'invalid': 'La fecha del asiento no esta en el formato correcto.'}
)
apuntes = ApunteSerializer(many=True)
class Meta:
model = Asiento
def create(self, data):
apuntes_data = data['apuntes']
asiento_data = data
asiento_data['fecha'] = datetime.datetime.strptime(asiento_data['fecha'], '%d-%m-%Y').date()
del asiento_data['apuntes']
asiento = Asiento.objects.create(**asiento_data)
for apunte in apuntes_data:
apunte['fecha'] = datetime.datetime.strptime(apunte['fecha'], '%d-%m-%Y').date()
Apunte.objects.create(asiento=asiento, **apunte)
My viewset:
class AsientoViewSet(viewsets.ModelViewSet):
queryset = Asiento.objects.all()
serializer_class = AsientoSerializer
def create(self, validated_data):
# JSON dictionary is inside validated_data.data
serializer = AsientoSerializer(data=validated_data.data)
if serializer.is_valid():
return Response(serializer.data)
else:
raise AttributeError('Error al validar')
This is JSON that I send with my request:
{
u'name': u'prueba 3000',
u'periodo': 13,
u'fecha': u'18-02-2016',
u'numero': None,
u'estado': u'borrador',
u'libro': 1,
u'apuntes': [
{
u'name': u'a',
u'recc': False,
u'debe': u'1',
u'haber': u'0.00',
u'cuenta': u'5',
u'partner': 8, u'fecha':
u'18-02-2016',
u'conciliacion': u''
}
]
}
If I debug the code only "create" method which executes is viewset's method, but it doesn't run "create" method from serializer.
With this code I can't save an "Asiento" neither an "Apunte". What am I doing wrong? Thank you for any answere!
I am using python 2.7 (I can't update it for external reasons) and Django REST Framework 3.3.2.

First thing you should do in case of invalid serialization is to look at the returned message (serializer.data). It'll help you understand what's going wrong.
My blind guess here is that you're missing the queryset argument relating to the nested serializer. If not set, DRF will consider those fields as read only.
Edit: After the OP edition, the issue comes from the viewset.
You need to call serializer.save() if the serializer is valid.
Have a look at how the CreateMixin does it.

Ok, I found that my problem was that "asiento" attribute in "Apunte" object can't be null and I wasn't sending it. Now, I have changed "asiento" attribute in "Apunte" object and serializer finally works, and it is no needed "create" method on ViewSet.
Thanks to everyone for your answeres.

Not sure if thats the solution. But the create method of a serializer has to return the created object.
Try adding return to your last line

Related

Python Django Queryset

I'm playing with querysets in django.
What I'm looking it's to save a new foreign product or item but I can not achieve it.
shell
from applaboratorio.models import Datos_empresa_DB, Datos_equipo_DB
detalle = Datos_empresa_DB.objects.filter(pk=58)
resp = Datos_equipo_DB(equipo='dell-labtop',marca='dell', modelo='432423',Foraneo_Datos_empresa_DB = detalle)
models.py
class Datos_empresa_DB(models.Model):
nombre = models.CharField(max_length=150)
empresa = models.CharField(max_length=150)
class Datos_equipo_DB(models.Model):
Foraneo_Datos_empresa_DB = models.ForeignKey(Datos_empresa_DB)
equipo = models.CharField(max_length=300)
marca = models.CharField(max_length=300)
modelo = models.CharField(max_length=300)
What am I doing bad?
I'm trying to create a new product for a client that already exist in db.
I think you're nearly there. You need to call the save method of the new product to save to the DB, and to retrieve the related client object, you should get not filter so you have the object itself and not a list of objects (or QuerySet):
detalle = Datos_empresa_DB.objects.get(pk=58)
# ^^^
resp = Datos_equipo_DB(equipo='dell-labtop',marca='dell', modelo='432423',Foraneo_Datos_empresa_DB =detalle)
# Save on model's related field <-^^^^^^^
resp.save()

Django rest framework nested serialization not working properly

I have these serializers in my app:
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ('id',)
class DisciplineSerializer(serializers.ModelSerializer):
class Meta:
model = Discipline
fields = ('id',)
class WriteTeacherSerializer(serializers.ModelSerializer):
disciplines = DisciplineSerializer(many=True)
schedules = ScheduleSerializer(many=True)
class Meta:
model = Teacher
fields = ('phone_number', 'bio', 'price', 'disciplines', 'schedules')
depth = 1
def update(self, instance, validated_data):
print "\n"
#Debugging here
print validated_data
print "\n"
print instance.__dict__
print "\n"
instance.phone_number = validated_data['phone_number']
instance.bio = validated_data['bio']
instance.price = validated_data['price']
disciplines = validated_data.pop('disciplines')
schedules = validated_data.pop('schedules')
for discipline in disciplines:
try:
stored_discipline = Discipline.objects.get(id=discipline['id'])
instance.disciplines.add(stored_discipline)
except Discipline.DoesNotExist:
raise Http404
for schedule in schedules:
try:
stored_schedule = Schedule.objects.get(id=schedule['id'])
instance.schedules.add(stored_discipline)
except Discipline.DoesNotExist:
raise Http404
instance.save()
return instance
As you can see I am trying a nested serialization with the fields schedules and disciplines. I think I followed the documentation, but the nested serialization is not working when I test it. I printed the instance and validated_data objects and tested it on the shell.
I start the data in this format:
data = {u'phone_number': u'+99999999999', u'bio': u'BIO', u'price': 40, u'disciplines': [{'id': 1}], u'schedules': [{'id': 2}]}
I got a teacher instance and started the serializer like this:
serializer = WriteTeacherSerializer(teacher, data=data)
It shows True on a serializer.is_valid() call.
However when I try to save it the validated_data and the instance.__dict__ are like that:
#validated_data
{u'phone_number': u'+5584998727770', u'bio': u'BIO', u'price': 40, u'disciplines': [OrderedDict()], u'schedules': [OrderedDict()]}
#instance.__dict__
{'phone_number': u'', 'bio': u'', 'price': 50, 'profile_id': 2, '_state': <django.db.models.base.ModelState object at 0xb64a6bec>, 'id': 6}
They don't seem to notice the nested fields wich makes the update() method not work.
Am I doing something wrong?
Here is my Teacher Model as well:
class Teacher(models.Model):
price = models.IntegerField(default=50)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Wrong phone number format.")
phone_number = models.CharField(validators=[phone_regex], max_length=15, blank=True)
profile = models.OneToOneField(Profile, on_delete=models.CASCADE)
schedules = models.ManyToManyField(Schedule, related_name='schedules')
disciplines = models.ManyToManyField(Discipline, related_name='disciplines')
bio = models.CharField(max_length=200, blank=True)
If you are just sending IDs then you don't need to add the nested serializer, just specify the field name of the ForeignKey or ManyToManyField.
class WriteTeacherSerializer(serializers.ModelSerializer):
class Meta:
model = Teacher
fields = ('phone_number', 'bio', 'price', 'disciplines', 'schedules')
I am also wondering if it is because you have a depth=1 flag?
DRF doesn't support nested updates out of the box. You have to override the Serializer's update method, and write your own update logic, so you'd be seeing an error for this warning if you were sending nested data.

Django how to get haystack working with django-hvad

I'm working on a multilingual application where I need to implement a search engine, I opted for haystack.
This is the model I want to index
class Post(TranslatableModel):
category = models.ForeignKey(
Category,
related_name='posts',
verbose_name=_(u'Categoría')
)
slug = AutoSlugField(
populate_from='title'
)
featured_picture = ThumbnailerImageField(
upload_to='blog/posts/featured-pictures',
verbose_name=_(u'Imagen destacada'),
help_text=_(u'Tamaño: 850 x 423px. Será recordada si no.')
)
box_bg = models.CharField(
max_length=20,
verbose_name=_(u'Color de fondo de la caja'),
choices=SERVICE_BOX_COLORS,
# Could be an image
null=True, blank=True
)
box_image_bg = ThumbnailerImageField(
upload_to='blog/posts/background-images',
verbose_name=_(u'Imagen de fondo de la caja'),
null=True, blank=True,
help_text=_(u'Tamaño: 500 x 500px. Será recordada si no.')
)
translations = TranslatedFields(
title=models.CharField(
max_length=100,
verbose_name=_(u'Título')
),
content=RichTextField(
verbose_name=_(u'Contenido')
),
meta_description=models.TextField(
verbose_name=_(u'Meta description (SEO)'),
help_text=_(u'Máximo 160 caracteres.'),
max_length=160
)
)
I don't know in haystack how to deal with hvad translation fields
I will describe what I did when faced a similar problem.
In your index definition, specify a field for the current language. Django-hvad always returns a 'language_code' field if you do a query, so for example:
class PostIndex(indexes.SearchIndex, indexes.Indexable):
# your field definitions here
lan = indexes.CharField(model_attr='language_code')
# rest of your configuration here
def index_queryset(self, using=None):
return self.get_model().objects.language('all').all()
def read_queryset(self, using=None):
return self.get_model().objects.language()
This way when Haystack indexes the Posts, it will get every language version separately and store it along with a language code.
You will have to modify your search form to include the lan field as a filter:
class PostSearchForm(SearchForm):
lan = forms.CharField(required=False)
def search(self):
sqs = super(PostSearchForm, self).search()
if not self.is_valid():
return self.no_query_found()
if self.cleaned_data.get('lan'):
sqs = sqs.filter(lan=self.cleaned_data['lan'])
return sqs
It can be a hidden input field, with the value taken from request.LANGUAGE_CODE.

Django Rest-Framework error using Post method with manytomany relationship

I have the next code:
Models:
class Producto(models.Model):
def __unicode__(self):
return(self.nombre)
nombre = models.CharField(max_length=50)
descripcion = models.TextField(max_length=140)
color = models.CharField(max_length=15)
talla = models.CharField(max_length=10)
precio = models.IntegerField()
cantidad = models.IntegerField()
create_date = models.DateTimeField("Fecha de Creacion",auto_now=True)
def precio_display(self):
return "Gs. "+ format(self.precio, "8,d")
precio_display.short_description = 'Precio'
class Venta(models.Model):
def __unicode__(self):
return "id: {0}, {1:%H:%M - %d/%m/%Y}".format(self.id, self.fecha)
fecha = models.DateTimeField(auto_now=True)
# Relacion muchos a muchos por medio de la tabla Detalle
productos = models.ManyToManyField('Producto', through="Detalle", related_name="productos")
total = models.IntegerField()
credencial = models.CharField(max_length=200)
class Detalle(models.Model):
producto = models.ForeignKey(Producto)
venta = models.ForeignKey(Venta)
cant = models.IntegerField()**`strong text`**
precioVenta = models.IntegerField()
def __unicode__(self):
return "Se vendio {0} de {1} en la venta {2}".format(self.cantidad,self.producto, self.venta.id)
Serializers:
class ProductoModelSerializer(ModelSerializer):
class Meta:
model = Producto
fields = ("id", "nombre", "descripcion", "color", "talla",
"precio", "cantidad")
class DetalleSerializer(HyperlinkedModelSerializer):
id = ReadOnlyField(source='producto.id')
nombre = ReadOnlyField(source='producto.nombre')
class Meta:
model = Detalle
fields = ('id', 'nombre', 'cant', 'precioVenta')
class VentaModelSerializer(ModelSerializer):
productos = DetalleSerializer(source='detalle_set', many=True)
class Meta:
model = Venta
fields = ("id", "productos", "total", "credencial")
and when i use Post method i get the next error:
TypeError at /stock/rest/ventas/
'Detalle' instance expected, got OrderedDict([(u'cant', 25), (u'precioVenta', 25000)])
Request Method: POST
Request URL: http://127.0.0.1:8000/stock/rest/ventas/
Django Version: 1.7.5
Exception Type: TypeError
Exception Value:
'Detalle' instance expected, got OrderedDict([(u'cant', 25), (u'precioVenta', 25000)])
i dont know why. The get method work perfectly when i use this with a web forntend done with angular.js. But the POST, PATCH and PUT method get me the same error.
PD: Sorry for my bad english.
The documentation says:
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
http://www.django-rest-framework.org/api-guide/serializers/
Thus you should write your own create method for POST in this case.

Django forms not saving to DB

I'm writing an app that has an HTML page which must be capable of saving data into 2 models. I've created two separate forms and referenced them in the view, however the information is not saving into the DB.
Here are the views.py
def nuevoingreso(request):
if request.method == "POST":
formingreso = NuevoIngreso(request.POST)
formprodingreso = NuevoProdIngreso(request.POST)
if formingreso.is_valid():
ingreso = formingreso.save(commit=False)
ingreso.idUser = request.user
ingreso.Condominio = get_object_or_404(Condominios, idCondominio=request.session["idCondominio"])
ingreso.save()
ingresoprod = formprodingreso.save()
for i in range(5):
if ProductosIngresos.SubtotalP != "" and ProductosIngresos.IvaP != "" and ProductosIngresos.TotalP != "":
ingresoprod.ProductosIngresos(Concepto=request.POST.get("Concepto"+str(i), ""), SubtotalP=request.POST.get("SubtotalP"+str(i), ""), IvaP=request.POST.get("IvaP"+str(i), ""), TotalP=request.POST.get("TotalP"+str(i), ""))
ingresoprod.save()
return HttpResponseRedirect("/propiedades/")
else:
return render(request, "immovelc/nuevoingreso.html",
{"formingreso": formingreso, "formprodingreso": formprodingreso})
propiedadesing = PropiedadesCond.objects.all()
context = ({"propiedadesing": propiedadesing})
return render(request, "immovelc/nuevoingreso.html", context)
forms.py
class NuevoIngreso(ModelForm):
class Meta:
model = Ingresos
fields = ["Numero", "Persona", "Fecha", "Observaciones", "Cobrado", "Subtotal", "Iva", "Total"]
def clean(self):
Numero = self.cleaned_data["Numero"]
Persona = self.cleaned_data["Persona"]
Fecha = self.cleaned_data["Fecha"]
if not Numero:
raise forms.ValidationError("El campo de numero es obligatorio")
if not Persona:
raise forms.ValidationError("El campo de cliente es obligatorio")
if not Fecha:
raise forms.ValidationError("El campo de fecha es obligatorio")
class NuevoProdIngreso(ModelForm):
class Meta:
model = ProductosIngresos
fields = ["Concepto", "SubtotalP", "IvaP", "TotalP"]
models.py
class Ingresos(models.Model):
idIngreso = models.AutoField(primary_key=True, null=False, max_length=15)
idCondominio = models.ForeignKey(Condominios)
idUser = models.ForeignKey(User)
Numero = models.CharField(max_length=100)
Persona = models.CharField(max_length=250, default="Ninguno")
Cobrado = models.CharField(max_length=100, default=0)
Observaciones = models.TextField(default="Ninguna")
Fecha = models.DateField()
Subtotal = models.CharField(max_length=100)
Iva = models.CharField(max_length=100)
Total = models.CharField(max_length=100)
def __unicode__(self):
return unicode(self.idIngreso)
class ProductosIngresos(models.Model):
idProductoIngreso = models.AutoField(primary_key=True, null=False, max_length=15)
idIngreso = models.ForeignKey(Ingresos)
Concepto = models.CharField(max_length=500)
SubtotalP = models.CharField(max_length=100)
IvaP = models.CharField(max_length=100)
TotalP = models.CharField(max_length=100)
def __unicode__(self):
return unicode(self.idProductoIngreso)
Thanks!
No offence, but this code is far from being correct.
Besides you've got many errors that you might want to remove.
Errors:
formprodingreso.is_valid() is never called
inside for i in range(5) you use a class as if it was an instance (ProductosIngresos.SubtotalP)
clean method in form has to be outside the Meta block
I believe what you want inside the loop is:
producto_ingreso = ProductosIngresos()
producto_ingreso.idIngreso = ingreso # better change to producto_ingreso.ingreso
producto_ingreso.Concepto=request.POST.get("Concepto"+str(i), "") # producto_ingreso.concepto
producto_ingreso.SubtotalP=request.POST.get("SubtotalP"+str(i), "") # producto_ingreso.subtotal_p
producto_ingreso.IvaP=request.POST.get("IvaP"+str(i), "")
producto_ingreso.TotalP=request.POST.get("TotalP"+str(i), ""))
producto_ingreso.save()
To make it cleaner, you can make this king of logic overridding the save() method of ModelForm. Or use inline formsets.
Confusion:
Model FKs are objects in Django, not integers. Better name them like condominio instead of idCondominio
Decimal columns (subtotal, iva, total) should be declared as deciaml i.e. models.DecimalField(max_digits=10, decimal_places=2)
clean method is intended for cross field validation (more than one field). Only one field should be validated by clean_numero f.e.
Over complication:
models have ID/PK by default, no need to explicit them (referenced as self.id or self.pk)
model unicode function is not giving any info
clean and ValidationError are superflous: modelform checks if attributes are requiered automatically
Convention errors:
attributes are always written_in_lowercase (SubtotalP -> subtotal_p)
I would seriously try to fix all of those if you dont want the developers maintaining your code hate you and make some voodoo on you.

Categories