first i update my model instance, after that i tried to insert a new data but showing
"IntegrityError('duplicate key value violates unique constraint "RFIDActivation_ActivationId_key"\nDETAIL: Key ("ActivationId")=(6de9ed9a) already exists.\n',)"
Models.py
class RFIDActivation(models.Model):
RFIDActivationId = models.AutoField(primary_key=True, db_column='RFIDActivationId')
Device = models.ForeignKey(Device, on_delete=models.CASCADE, db_column='DeviceId')
Employee = models.ForeignKey(Employee, on_delete=models.CASCADE, db_column='EmployeeId')
ActivationId = models.CharField(max_length=10, unique=True, default=uuid4().hex[:8])
ActivationStatus = models.CharField(max_length=1)default=None)
class Meta:
db_table = "RFIDActivation"
my serializer.py
class RFIDActivationSerializer(serializers.ModelSerializer):
class Meta:
model = RFIDActivation
fields = '__all__'
view.py
#api_view(["POST"])
#permission_classes([IsAuthenticated])
def rfid_activation_initial(request):
RFIDActivation.objects.filter(Employee=request.POST.get("Employee")).update(
ActivationStatus='2',
ActivationMessage='Abort'
)
rfid_activation = {
'Employee': request.POST.get("Employee"),
'Device': request.POST.get("Device"),
'ActivationStatus': "0",
'ActivationMessage': "RFID Activation Initiated"
}
rfid_serializer = RFIDActivationSerializer(data=rfid_activation)
if rfid_serializer.is_valid():
rfid_serializer.save()
but rfid_serializer.save() shows exceptionexception
Previous ActivationId is using to saving the new data. ActivationId is unique and auto generated. How can i solve this. Error is showing trying to insert after update query
view.py for working fine code only insertion.
#api_view(["POST"])
#permission_classes([IsAuthenticated])
def rfid_data_add(request):
rfid_activation = {
'Employee': request.POST.get("Employee"),
'Device': request.POST.get("Device"),
'ActivationStatus': "0",
'ActivationMessage': "RFID Activation Initiated"
}
rfid_serializer = RFIDActivationSerializer(data=rfid_activation)
if rfid_serializer.is_valid():
rfid_serializer.save()
In restframework, serializer.save() has different behavior based on creation and update. If you pass instance to serializer, it will update the object, otherwise, it will create a new one
#api_view(['POST', 'PUT'])
def rfid_data(request, pk):
"""
Update or delete a rfid data.
"""
rfid_activation = {
...
}
if request.method == 'POST':
rfid_serializer = RFIDActivationSerializer(data=rfid_activation)
if rfid_serializer.is_valid():
rfid_serializer.save()
return Response(rfid_serializer.data,status=status.HTTP_201_CREATED)
return return Response(rfid_serializer.data, status=status.HTTP_400_BAD_REQUEST)
# PUT is used for object update
elif request.method == 'PUT':
rfid = RFIDActivation.objects.get(pk=pk)
# pass instance to your serializer
# pass partial=True to allow partial updates
rfid_serializer = RFIDActivationSerializer(instance=rfid, data=rfid_activation, partial=True)
if rfid_serializer.is_valid():
rfid_serializer.save()
return Response(rfid_serializer.data)
return Response(rfid_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
UPDATE
default is calculated at import time, in your case, your default value for activation_id is calculated once and never change for subsequent instances.
You have to define a function, thus default callable will be updated each time on object creation, read more here
def activation_id_generator(self):
return uuid4().hex[:8]
# then in your class
class RFIDActivation(models.Model):
...
ActivationId = models.CharField(max_length=10, unique=True, default=activation_id_generator)
Related
In Django Rest Framework ListSerializer when I want to save the validated data to the database by calling instance.save() I'm getting an error saying queryset object has no attribute save.
ListSerializer class:
class NoAccessDetailsListSerializer(serializers.ListSerializer):
# This will be called when there is list of objects
#here instance is list of queryset and validated_data is the list of json object
def update(self, instance, validated_data):
ret = []
for index, data in enumerate(validated_data):
#checking if row is already chosen
if(instance[index].row_chosen):
# do not update the info to db
# just append the data to ret
ret.append(instance[index])
else:
instance.id = instance[index].id
instance.row_chosen = validated_data[index].get(
'row_chosen')
instance.user_working = validated_data[index].get(
'user_working')
ret.append(instance)
instance.save()
return ret
Serializer Class
class NoAccessDetailsSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=False)
class Meta:
model = NoAccessDetails
list_serializer_class = NoAccessDetailsListSerializer
fields = ("id", "row_chosen",
"user_working")
def update(self, instance, validated_data):
instance.id = instance.id
instance.row_chosen = validated_data.get(
'row_chosen')
instance.user_working = validated_data.get(
'user_working ')
instance.save()
return instance
Basically in ListSerializer I'm checking if the row is chosen already in the DB. If True then I just append the instance data to a dictionary else I want to update the data to the DB and append the updated data to a list and return it.
Here in the ListSerializer I'm passing filtered queryset from the APIView class as instance and validated_data is a list of validated data.
Sample JSON data which I will pass to the APIView class:
[
{
"id": 1,
"row_chosen": true,
"user_working": "John"
},
{
"id": 1,
"row_chosen": true,
"user_working": "David"
},
]
When I pass the JSON data, it will properly filter out the rows from DB and pass the queryset as instance and JSON data to the serializer class.
# here list_of_id is the ids which are there in the JSON object. i.e [1,2]
filtered_id_data= NoAccessDetails.objects.filter(
id__in=list_of_id)
serializer = NoAccessDetailsSerializer(filtered_id_data,
data=request.data,
many=True,
)
The ListSerializer update() is working but when it runs else block and tries to update the data it gives me an error queryset object has no attribute save. Whereas in the serializer's update() it runs the instance.save() and updates the data for the single object. I'm not sure where I'm making the mistake.
Please help me with this.
Update:
I changed instance.save() to instance[index].save() in ListSerializer class. Now the queryset object has no attribute save has been fixed. Even though when I use instance[index].save() I'm unable to save the data in the data base.
Models:
class NoAccessDetails(models.Model):
20 CharFields
...
...
user_working = models.ForeignKey(
UserProfile, on_delete=models.CASCADE, blank=True, null=True)
row_chosen = models.BooleanField(default=False)
class UserProfile(models.Model):
user_id = models.CharField(primary_key=True, max_length=10)
user_name = models.CharField(max_length=100)
user_email = models.EmailField()
is_admin = models.BooleanField(default=False)
Here in the NoAccessDetail model, I've kept user_working null true because the data to this model will be coming from a different source. Initially while importing the data the user_working will be null. While updating the data from an API call, I'm validating the JSON data.
To call the .save() method, you have to call it on an instance of a Model, not on a QuerySet of the model. According to DRF Docs,
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# Maps for id->instance and id->data item.
book_mapping = {book.id: book for book in instance}
data_mapping = {item['id']: item for item in validated_data}
# Perform creations and updates.
ret = []
for book_id, data in data_mapping.items():
book = book_mapping.get(book_id, None)
if book is None:
ret.append(self.child.create(data))
else:
ret.append(self.child.update(book, data))
# Perform deletions.
for book_id, book in book_mapping.items():
if book_id not in data_mapping:
book.delete()
return ret
You can see they are using a book_mapping. This is creating a dictionary where the key is the book's id and the value is the instance of the book.
Hope this helps!
EDIT
Check the line just below the 'else:' block. You see you need to use .get() to get the object of the model you want to update, and then use the .save() method.
RE-EDIT
Using instance[index].save() should also work. I think you need to call obj.save() before appending to ret.
class NoAccessDetailsListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
...
...
else:
obj = NoAccessDetails.objects.get(id=instance[index].id)
# or obj = instance[index]
obj.row_chosen = validated_data[index].get(
'row_chosen')
obj.user_working = validated_data[index].get(
'user_working')
print('Instance data ',
obj.row_chosen,
obj.user_working)
obj.save() # call this before appending to ret
ret.append(obj)
return ret
RE-RE-EDIT
I updated the snippet according to docs.
class NoAccessDetailsListSerializer(serializers.ListSerializer):
# This will be called when there is list of objects
# here instance is list of queryset and validated_data is the list of json object
def update(self, instance, validated_data):
ret = []
obj_mapping = {obj.id: obj for obj in instance}
data_mapping = {item['id']: item for item in validated_data}
for obj_id, data in data_mapping.items():
obj = obj_mapping.get(obj_id, None)
if not obj:
continue
if obj.row_chosen:
ret.append(obj)
else:
obj.row_chosen = data['row_chosen']
obj.user_working = data['user_working']
obj.save()
ret.append(obj)
return ret
I have two models like this:
class Sector(models.Model):
name = models.CharField(max_length=100, db_index=True, unique=True) # HERE IF I REMOVE unique=True, it works correctly
class Address(models.Model):
...
sector = models.ForeignKey(Sector, null=True, blank=True)
And a serializer for the Address model:
In the view, I have this:
address_serialized = AddressSerializer(data=request.data)
if address_serialized.is_valid():
address_serialized.save(client=client)
It never gets to the create function. I have a serialized with a create function that looks like this:
class AddressSerializer(serializers.ModelSerializer):
city_gps = CitySerializer(required=False)
sector = SectorSerializer(required=False)
class Meta:
model = Address
fields = (..., "sector")
def create(self, validated_data):
...
sector_dict = validated_data.get("sector", None)
sector = None
if sector_dict and "name" in sector_dict and city_gps:
if Sector.objects.filter(name=sector_dict["name"], city=city_gps).exists():
sector = Sector.objects.get(name=sector_dict["name"], city=city_gps)
# pdb.set_trace()
if "sector" in validated_data:
validated_data.pop("sector")
if "city_gps" in validated_data:
validated_data.pop("city_gps")
address = Address.objects.create(sector=sector, city_gps=city_gps, **validated_data)
return address
The code never touches this function, is_valid() returns False. And the message is
{"sector":{"name":["sector with this name already exists."]}}
I need to be able to create a new address with FK to the already existing sector. How can I achieve that? Any advice will help.
EDIT
The view looks like this:
class ClientProfileAddressCreateView(APIView):
# throttle_scope = '1persecond'
renderer_classes = (JSONRenderer,)
permission_classes = (IsAuthenticated,)
def post(self, request):
try:
client = Client.objects.get(user=request.user)
except ObjectDoesNotExist:
return Response({"error": "A client profile for the logged user does not exit"},
status=status.HTTP_404_NOT_FOUND)
address_serialized = AddressSerializer(data=request.data)
print("address_serialized.is_valid: %s" % address_serialized.is_valid()) # Returns False when unique=True in models
if address_serialized.is_valid():
# print("address_serialized: %s" % address_serialized.data)
address_serialized.save(client=client)
else:
return Response(data=address_serialized.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(data=address_serialized.data, status=status.HTTP_201_CREATED)
This is a known issue with nested serializers and unique constraints.
Really awesome thing to always do is actually print the Serializer - that can give you a lot of extra info.
When you have a json like this:
{
"Sector": {
"name": "Sector XYZ"
},
"address_line_one": “Some Random Address”
}
Django REST framework does not know whether you're creating or getting the Sector object, thus it forces validation on every request.
What you need to do is the following:
class SectorSerializer(serializers.ModelSerializer):
# Your fields.
class Meta:
model = Address
fields = ("Your Fields",)
extra_kwargs = {
'name': {
'validators': [],
}
}
Then to handle validation you would need to redo the create/update part to fit the uniqueness constraint and raise exception/validation error.
I hope this helps.
Helpful links: This SO Answer and Dealing with unique constraints in nested serializers
EDIT :
As per cezar's request: I will add how it might look like to override the create method of the serializer. I have not tried this code, but the logic goes like this.
class SectorSerializer(serializers.ModelSerializer):
# Your fields.
class Meta:
model = Address
fields = ("Your Fields",)
extra_kwargs = {
'name': {
'validators': [],
}
}
def create(self, validated_data):
raise_errors_on_nested_writes('create', self, validated_data)
ModelClass = self.Meta.model
info = model_meta.get_field_info(ModelClass)
many_to_many = {}
for field_name, relation_info in info.relations.items():
if relation_info.to_many and (field_name in validated_data):
many_to_many[field_name] = validated_data.pop(field_name)
# FIELD CHECK
your_field = validated_data.get("your_field","") # or validated_data["your_field"]
try:
YourModel.objects.filter(your_check=your_field).get()
raise ValidationError("Your error")
except YourModel.DoesNotExist:
# if it doesn't exist it means that no model containing that field exists so pass it. You can use YourQuerySet.exists() but then the logic changes
pass
try:
instance = ModelClass.objects.create(**validated_data)
except TypeError:
tb = traceback.format_exc()
msg = (
'Got a `TypeError` when calling `%s.objects.create()`. '
'This may be because you have a writable field on the '
'serializer class that is not a valid argument to '
'`%s.objects.create()`. You may need to make the field '
'read-only, or override the %s.create() method to handle '
'this correctly.\nOriginal exception was:\n %s' %
(
ModelClass.__name__,
ModelClass.__name__,
self.__class__.__name__,
tb
)
)
raise TypeError(msg)
# Save many-to-many relationships after the instance is created.
if many_to_many:
for field_name, value in many_to_many.items():
field = getattr(instance, field_name)
field.set(value)
return instance
I am using rest_framework v3.1.3 in django 1.8. I am pretty new to django.
Here are the relevant model definitions
#python_2_unicode_compatible
class UserFitbit(models.Model):
user = models.OneToOneField(User, related_name='fituser')
fitbit_user = models.CharField(max_length=32)
auth_token = models.TextField()
auth_secret = models.TextField()
#this is a hack so that I can use this as a lookup field in the serializers
#property
def user__userid(self):
return self.user.id
def __str__(self):
return self.user.first_name + ' ' + self.user.last_name
def get_user_data(self):
return {
'user_key': self.auth_token,
'user_secret': self.auth_secret,
'user_id': self.fitbit_user,
'resource_owner_key': self.auth_token,
'resource_owner_secret': self.auth_secret,
'user_id': self.fitbit_user,
}
def to_JSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
class Challenge(models.Model):
name=models.TextField()
status=models.TextField() #active, pending, ended, deleted
start_date=models.DateField()
end_date=models.DateField()
#members=models.ManyToManyField(UserFitbit)
members=models.ManyToManyField(User)
admin=models.ForeignKey(UserFitbit,related_name='admin')
#for each member get stats between the start and end dates
def memberstats(self):
stats = []
for member in self.members.all():
fbu = UserFitbit.objects.filter(user__id=member.id)
fu = UserData.objects.filter(userfitbit=fbu)
fu = fu.filter(activity_date__range=[self.start_date,self.end_date])
fu = fu.annotate(first_name=F('userfitbit__user__first_name'))
fu = fu.annotate(user_id=F('userfitbit__user__id'))
fu = fu.annotate(last_name=F('userfitbit__user__last_name'))
fu = fu.values('first_name','last_name','user_id')
fu = fu.annotate(total_distance=Sum('distance'),total_steps=Sum('steps'))
if fu:
stats.append(fu[0])
return stats
def __str__(self):
return 'Challenge:' + str(self.name)
class Meta:
ordering = ('-start_date','name')
And here is the serializer for the challenge
class ChallengeSerializer(serializers.ModelSerializer):
links = serializers.SerializerMethodField(read_only=True)
memberstats = MemberStatSerializer(read_only=True,many=True)
#these are user objects
#this should provide a hyperlink to each member
members = serializers.HyperlinkedRelatedField(
#queryset defines the valid selectable values
queryset=User.objects.all(),
view_name='user-detail',
lookup_field='pk',
many=True,
)
class Meta:
model=Challenge
fields = ('id','name','admin','status','start_date','end_date','members','links','memberstats',)
read_only_fields = ('memberstats','links',)
def get_links(self, obj) :
request = self.context['request']
return {
'self': reverse('challenge-detail',
kwargs={'pk':obj.pk},request=request),
}
As you can see the Challenge has a many to many relationship with User. This is the built in User model from django not UserFitBit defined here.
With these definitions when I go to the api browser for a challenge I need to be able to select the users based on their name, but the select only shows their User id property and the hyperlink url. I would like the members to be User objects, but I don't know how to change the text for the select options since I don't think I can change the built in User object. What is the best way to change the select box options to show the users name from the User object rather than the username field and hyperlink?
Here is an image:
I'm not sure if this is the best way but after reading DRF's source code, I would try this.
Subclass the HyperlinkedRelatedField and override the choices property.
import six
from collections import OrderedDict
class UserHyperLinkedRelatedField(serializers.HyperLinkedRelatedField):
#property
def choices(self):
queryset = self.get_queryset()
if queryset is None:
return {}
return OrderedDict([
(
six.text_type(self.to_representation(item)),
six.text_type(item.get_full_name())
)
for item in queryset
])
then would simply replace the field in the serializer.
members = UserHyperlinkedRelatedField(
queryset=User.objects.all(),
view_name='user-detail',
lookup_field='pk',
many=True,
)
The DRF docs also mentioned that there's a plan to add a public API to support customising HTML form generation in future releases.
Update
For DRF 3.2.2 or higher, there will be an available display_value method.
You can do
class UserHyperLinkedRelatedField(serializers.HyperLinkedRelatedField):
def display_value(self, instance):
return instance.get_full_name()
Because this is a many related field I also had to extend the ManyRelatedField and override the many_init method of the RelatedField to use that class. Can't say I understand all of this just yet, but it is working.
class UserManyRelatedField(serializers.ManyRelatedField):
#property
def choices(self):
queryset = self.child_relation.queryset
iterable = queryset.all() if (hasattr(queryset, 'all')) else queryset
items_and_representations = [
(item, self.child_relation.to_representation(item))
for item in iterable
]
return OrderedDict([
(
six.text_type(item_representation),
item.get_full_name() ,
)
for item, item_representation in items_and_representations
])
class UserHyperlinkedRelatedField(serializers.HyperlinkedRelatedField):
#classmethod
def many_init(cls, *args, **kwargs):
list_kwargs = {'child_relation': cls(*args, **kwargs)}
for key in kwargs.keys():
if key in MANY_RELATION_KWARGS:
list_kwargs[key] = kwargs[key]
return UserManyRelatedField(**list_kwargs)
members = UserHyperlinkedRelatedField(
queryset=User.objects.all(),
view_name='user-detail',
lookup_field='pk',
many=True,
)
I am using django and as I am pretty new I have some questions.
I have one model called Signatures and a ModelForm called SignatureForm in my models.py file:
class Signature(models.Model):
sig = models.ForeignKey(Device)
STATE = models.CharField(max_length=3, choices=STATE_CHOICES)
interval = models.DecimalField(max_digits=3, decimal_places=2)
verticies = models.CharField(max_length=150)
class SignatureForm(ModelForm):
class Meta:
model = Signature
widgets = {
'verticies': HiddenInput,
}
To use it, I wrote the following function in views.py:
def SigEditor(request):
# If the form has been sent:
if request.method == 'POST':
form = SignatureForm(request.POST)
# If it is valid
if form.is_valid():
# Create a new Signature object.
form.save()
return render_to_response('eQL/sig/form_sent.html')
else:
return render_to_response('eQL/sig/try_again.html')
else:
form = SignatureForm()
return render_to_response('eQL/sig/showImage.html', {'form' : form})
However, I don't want to save all the new signatures. I mean, if the user introduces a new signature of the device A and state B, I would like to check if I have some signature like that in my database, delete it and then save the new one so that I have only one signature saved for each device and state.
I have tried something like this before saving it but of course is not working:
q = Signature.objects.filter(sig = s, STATE = st)
if q.count != 0:
q.delete()
form.save()
can anyone help?? thanks!!
If you really do want to delete, why not?
Signature.objects.filter(sig=s, STATE=st).delete()
If you only ever want one combination of those items, you could use get_or_create, and pass in the instance to your ModelForm.
instance, created = Signature.objects.get_or_create(sig=s, STATE=st)
form = SignatureForm(request.POST, instance=signature)
# edit instance.
Or put it in your form save logic:
class SignatureForm(ModelForm):
def save(self, *args, **kwargs):
data = self.cleaned_data
instance, created = Signature.objects.get_or_create(sig=data['sig'], STATE=data['state'])
self.instance = instance
super(SignatureForm, self).save(*args, **kwargs)
I have the following problem.
I have a model Towar:
class Towar(models.Model):
nrSeryjny = models.CharField(max_length=100)
opis = models.CharField(max_length=255)
naStanie = models.NullBooleanField(null=True)
def __unicode__(self):
return "%s" % self.opis
def lowerName(self):
return self.__class__.__name__.lower()
def checkState(self):
return self.naStanie
def changeState(self,state):
self.naStanie=state
class Meta:
ordering=['nrSeryjny']
app_label = 'baza'
permissions=(("view_towar","mozna miec podglad dla towar"),)
and another model, Wypozyczenie, which is linked to Towar by a foreign key relationship:
class Wypozyczenie(models.Model):
dataPobrania = models.DateField()
pracownik = models.ForeignKey(User,null=True)
kontrahent = models.ForeignKey(Kontrahenci,null=True)
towar = models.ForeignKey(Towar,null=True)
objects = WypozyczenieManager()
default_objects = models.Manager()
ZwrotyObjects = WypozyczenieZwrotyManager()
def lowerName(self):
return self.__class__.__name__.lower()
def __unicode__(self):
if self.towar == None:
return "Dla:%s -- Kto:%s -- Kiedy:%s -- Co:%s" % (self.kontrahent,self.pracownik,self.dataPobrania,"Brak")
else:
return "Dla:%s -- Kto:%s -- Kiedy:%s -- Co:%s" % (self.kontrahent,self.pracownik,self.dataPobrania,self.towar)
class Meta:
ordering = ['dataPobrania']
app_label = 'baza'
permissions = (("view_wypozyczenie","mozna miec podglad dla wypozyczenie"),)
and a view to add models:
def modelAdd(request,model,modelForm):
mod = model()
if request.user.has_perm('baza.add_%s' % mod.lowerName()):
if request.method == 'POST':
form=modelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/'+ mod.lowerName() + '/')
else:
form = modelForm()
v = RequestContext(request,{'form':form})
return render_to_response('add_form.html',v)
What I want is that when I add Wypozyczenie and save it, then the Towar that is stored by Wypozyczenie changes its naStanie field from True to False.
Greets
If you want to always keep those two in sync you can override Wypozyczenie's save() method.
class Wypozyczenie(models.Model):
...
def save(self):
self.towar.naStanie = False
self.towar.save()
Alternatively, you can also override ModelForm's save() method.
What have you tried?
Can't you just put
myinstance = form.save()
towar = myinstance.towar
towar.naStanie = False
toware.save()
instead of your simple call to form.save() in your view.
You can use signals emitted when saving your Wypozyczenie object. It might be a little "cleaner" than overriding save(), especially when it's useful to re-use the function for other models:
#receiver(models.signals.post_save, sender=Wypozyczenie)
def after_wypozyczenie_save(sender, instance, created, **kwargs):
# `instance` is your saved Wypozyczenie object, you can
# access all it's fields here:
instance.towar.naStanie = False
instance.towar.save()
# It's also possible to do different action on the first save
# and on subsequent updates:
#
# if created:
# ...
# else:
# ...
There are other signals sent before saving or on deletion. Django documentation on signals is quite helpful here.