unique_together does not prevent duplicate records - django

I have two models like this :
class Preference(models.Model):
lat = models.DecimalField(max_digits=25,decimal_places=15)
lng = models.DecimalField(max_digits=25,decimal_places=15)
name = models.CharField(max_length=150,null=True)
address = models.TextField(max_length=350,null=True)
type = models.CharField(max_length=150,blank=True,null=True)
class PrefenceOfUser(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
place_detail = models.ForeignKey(Preference, on_delete=models.CASCADE)
def __str__(self):
return self.user.username
class Meta:
unique_together = ('user', 'place_detail',)
this is the json i post to my apiview :
{
"lat": "29.621142463088336",
"lng": "52.520185499694527",
"name":"cafesama1",
"address":"streetn1",
"type":"cafe"
}
in views.py :
class PreferedLocationsOfUsers(APIView):
def post(self, request, format=None):
serializer = PreferLocationSerializer(data=request.data)
if serializer.is_valid():
location= Preference(**serializer.data)
location.save()
user_perefeces = PrefenceOfUser(user=request.user,place_detail=location)
user_perefeces.save()
return Response({'location saved'},status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
i want to prevent the user to save dublicated records in database but when location object is saved in PrefenceOfUser unique_together does not prevent dublicating. any idea why?

You do have a migration issue as per the comments. Get rid of the duplicates, re-run the migration until you get no errors and you're fine.
However, I would turn this around as a model design problem. You shouldn't need to resort to manual constraints for this.
class Preference(models.Model):
lat = models.DecimalField(max_digits=25,decimal_places=15)
lng = models.DecimalField(max_digits=25,decimal_places=15)
name = models.CharField(max_length=150,null=True)
address = models.TextField(max_length=350,null=True)
type = models.CharField(max_length=150,blank=True,null=True)
users = models.ManyToManyField(get_user_model(), blank=True)
This above code will have exactly the same implications as yours, is cleaner and more Djangoesque.
You can still access what you call PrefenceOfUser through Preference.users.through. If you plan to add more attributes to the selection (ie. when did user add their preference), you can just do:
class PrefenceOfUser(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
place_detail = models.ForeignKey(Preference, on_delete=models.CASCADE)
extra_field = models.WhateverField()
def __str__(self):
return self.user.username
and change the Preference.users to
models.ManyToManyField(get_user_model(), through=PrefenceOfUser)
which will still still ensure the uniqueness.

Related

Error after overriding create method in serializer

I'm Overriding create method of serializer in order to manipulate validated_data and create object in a model, Although it works, in the end I get below error, i am not able to figure out why after lot of research.
AttributeError: Got AttributeError when attempting to get a value for field `shift_time` on serializer `PunchRawDataAndroidSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `PunchRawData` instance.
Original exception text was: 'PunchRawData' object has no attribute 'shift_time'.
class PunchRawDataAndroidSerializer(serializers.ModelSerializer):
employee_id = serializers.CharField()
shift_id = serializers.CharField()
work_location_id = serializers.CharField()
shift_time = serializers.TimeField()
class Meta:
model = PunchRawData
fields = ['employee_id', 'shift_id','work_location_id', 'punch_type', 'actual_clock_datetime',
'emp_photo', 'created_at', 'updated_at','shift_time']
def create(self, validated_data):
validated_data.pop('shift_time')
request_data = self.context.get('request')
user = request_data.user
validated_data['user'] = user
data = validated_data
return PunchRawData.objects.create(**data)
class PunchRawDataAndroidViewSet(viewsets.ModelViewSet):
serializer_class = PunchRawDataAndroidSerializer
parser_classes = (MultiPartParser, FileUploadParser)
edit:
class PunchRawData(models.Model):
PUNCH_TYPES = [("in", "Punch IN"), ("out", "Punch Out")]
employee = models.ForeignKey(Employee, related_name="punch_employee", on_delete=models.CASCADE)
shift = models.ForeignKey(WorkShift, on_delete=models.CASCADE)
work_location = models.ForeignKey(HRMLocation, blank=True, on_delete=models.CASCADE,
null=True, related_name="punch_work_location")
punch_type = models.CharField(max_length=255, null=True, blank=True, choices=PUNCH_TYPES)
user = models.ForeignKey("useraccounts.User", on_delete=models.CASCADE)
actual_clock_datetime = models.DateTimeField(null=True, blank=True)
emp_photo = models.ImageField(upload_to="selfies/%Y/%m/%d/%I/%M/%S/")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
strl = "{emp_id} [{shift_id}]".format(emp_id=self.employee.emp_id,
shift_id=self.shift.shift_id)
return strl
class Meta:
verbose_name = "Punch Raw Data"
verbose_name_plural = "Punch Raw Data"
I get shift_time from frontend and it is not from model, hence i'm poping it out from validated_data in create method. is error related to modelviewset?
Your model doesn't have the shift_time attribute. So if you try to save it, you will end with
PunchRawData() got an unexpected keyword argument 'shift_time'
At the other hand you are getting AttributeError, because serializers.to_representation() tries to get a non-existing attribute when showing your freshly saved object.
If this should be a read-only attribute, you may do the following:
shift_time = serializers.TimeField(read_only=True)
and than remove the
validated_data.pop('shift_time')
from PunchRawDataAndroidSerializer.create(). You don't need this any more, because it is never submitted from your client.
If you need the opposite – your client should provide you that field, but you don't want it saved in your model, than the only thing, you should do, is:
shift_time = serializers.TimeField(write_only=True)
And if you need it to be bidirectional, than you should add it to your model.
Hope this helps.
Adding to #wankata's answer we can override __init__ method to have write_only field for only create method.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['view'].action == 'create':
self.fields['shift_time'].write_only = True
Generic viewsets of django-rest-framework return the serialized representation of the model in response, so it's try to serialize the model including the shift_time key.
To avoid this problem you can specify the shift_time field as write_only. documentation
modify the Meta class on your model
class Meta:
model = PunchRawData
fields = ['employee_id', 'shift_id','work_location_id', 'punch_type', 'actual_clock_datetime',
'emp_photo', 'created_at', 'updated_at','shift_time']
extra_kwargs = {'shift_time': {'write_only': True}}

Django Rest Framework - How to set the current_user when POST

I have just followed a small tutorial using DRF, but I can't figure how to like my model to his user when POSTing a new object
this is my model :
# Create your models here.
class Project(models.Model):
# project title
title = models.CharField(max_length=255, null=False)
# subtitle
subtitle = models.CharField(max_length=255, null=False)
#######
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
and so my serializer
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ("id", "title", "subtitle", "user_id")
so, now in the view I have access to the current_user with this :
request.user
class ListProjectsView(generics.ListCreateAPIView):
authentication_classes = [authentication.TokenAuthentication]
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def list(self, request):
queryset = Project.objects.filter(user_id=request.user.id)
serializer = ProjectSerializer(queryset, many=True)
return Response(serializer.data)
[...]
def create(self, request, pk = None):
return super(ListProjectsView, self).create(request, pk = None)
I suppose there is a way to passe the request.user is the create in order to allow my Project.user_id to be filled ?
Whatever I'm doing, I can't manage to set the user, and i get the
django.db.utils.IntegrityError: null value in column "user_id" violates not-null constraint error
Any idea?
Thanks!
Try to override with following method. Everytime PUT/PATCH/CREATE operation is performed following method is called. This is the good way to pass the current user.
def perform_create(self, serializer):
serializer.save(user = self.request.user)
class Project(models.Model):
# project title
title = models.CharField(max_length=255, null=False)
# subtitle
subtitle = models.CharField(max_length=255, null=False)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
okay so you have FK user but try to access it with the user_id = request.user.id
Just call
queryset = Project.objects.filter(user=request.user)
queryset = Project.objects.filter (user_id=request.user.id)
if you want to match the id you should put two __
like so user__id = request.user.id but I dont see any sence making it.

Polymorphic models serializer

I'm using a Polymorphic model for setting up notifications:
My models:
class Notification(PolymorphicModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="creatednotifications")
created_on = models.DateTimeField(default=timezone.now)
created_for = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="receivednotifications")
read = models.DateTimeField(default=None, null=True, blank=True)
message = models.CharField(default=None, blank=True, null=True, max_length=800)
#property
def total(self):
return self.objects.filter(created_for=self.request.user).count()
#property
def unread(self):
return self.objects.filter(created_for=self.request.user,read=None).count()
#property
def read(self):
return self.objects.filter(created_for=self.request.user).exclude(read=None).count()
class WorkflowNotification(Notification):
# permission_transition = models.ForeignKey(WorkflowStatePermissionTransition, on_delete=models.CASCADE)
action = models.ForeignKey(UserAction, on_delete=models.CASCADE)
Currently i have just one model WorkFlowNotification inheriting from the Polymorphic model,but many would be there in the future.
Im trying to get the count(total) of notifications for the logged in user in the API ..total is given as property field to help in the same
my serializer:
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.ReadOnlyField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['id', 'total','read', 'unread']
In the view:
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
queryset = Notification.objects.all()
When i try to run the server it shows:
Got AttributeError when attempting to get a value for field `total` on serializer `NotificationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `WorkflowNotification` instance.
Original exception text was: Manager isn't accessible via WorkflowNotification instances.
Since you need the 'meta data' only, what is the use of making a model serializer? Or any serializer, for that matter? Serializers will give you serialized instances of the objects of your model. So if you have multiple objects, you will get multiple serialized objects in response.
Just make your view a normal APIView. Since there is no necessity of serializing anything.
class NotificationsMeta(APIView):
def get(self, request, format=None):
qs = Notification.objects.filter(created_for=self.request.user)
response = {
'total': qs.count(),
'read': qs.filter(read=None).count(),
'unread': qs.exclude(read=None).count()
}
return Response(response)
Now remove those property functions from your model.
I didn't test your queries, just copied them from your model. You will need to check if they are working properly. Hope this helps.
I am not sure about how calling a model property who is responsible for querying in model can give appropriate data from serializer. Unfortunately i do have knowledge gap about that. I am thinking about an alternative solution. I hope following should work.
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.serializers.SerializerMethodField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['read', 'unread']
def get_total(self, obj):
user = self.context['request'].user
return Notification.objects.filter(created_for=user).count()
If this work then you can able to do similar kind of thing for read and unread too.
In order to get notification for current_user we need to overwrite get_queryset from view.
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
return Notification.objects.filter(created_for=self.request.user)

Two step object creation in Django Admin

I'm trying to change the implementation of an EAV model using a JSONField to store all the attributes defined by an attribute_set.
I already figured out how to build a form to edit the single attributes of the JSON, but I'm currently stuck at implementing the creation of a new object. I think I have to split object creation in two steps, because I need to know the attribute_set to generate the correct form, but I don't know if there's a way to hook in the create action, or any other way to achieve what I need.
My models look like this:
class EavAttribute(models.Model):
entity_type = models.CharField(max_length=25, choices=entity_types)
code = models.CharField(max_length=30)
name = models.CharField(max_length=50)
data_type = models.CharField(max_length=30, choices=data_types)
class AttributeSet(models.Model):
name = models.CharField(max_length=25)
attributes = models.ManyToManyField('EavAttribute')
class EntityAbstract(models.Model):
attribute_set = models.ForeignKey(
'AttributeSet',
blank=False,
null=False,
unique=False,
)
class Meta:
abstract = True
class Event(EntityAbstract):
entity_type = models.CharField(max_length=20, null=False, choices=entity_types, default=DEFAULT_ENTITY_TYPE)
code = models.CharField(max_length=25, null=True, blank=True, db_index=True)
year = models.IntegerField(db_index=True)
begin_date = models.DateField()
end_date = models.DateField()
data = JSONField()
How can I choose the AttributeSet first and then go to another form that I would populate with the attributes in the chosen attribute set?
I ended up using get_fields() and response_add() methods, like so:
def get_fields(self, request, obj=None):
if obj is None:
return ['attribute_set']
else:
return [attr.name for attr in obj._meta.get_fields() if not attr.auto_created and attr.name != 'id']
def get_readonly_fields(self, request, obj=None):
readonly_fields = ['entity_type', 'code', 'state']
if obj is not None:
readonly_fields.append('attribute_set')
return readonly_fields
def response_add(self, request, obj, post_url_continue=None):
url = '/admin/risks/event/{}/change/'.format(obj.id)
return redirect(url)
The downside of this approach is that object is saved in the database and then opened for edit, so basically the database is hit twice and all attributes have to be nullable, except for attribute_set.
I would be happy to receive ideas for better implementations.

How to set automatically 'many' flag of django serializer depending on input being a list or a single item

I have the following machine model.
class Machine(models.Model):
operators = models.ManyToManyField(User, related_name='machines', blank=True)
elasticsearch_id = models.PositiveIntegerField(default=None, null=True, blank=True)
company = models.ForeignKey(Company, default=None, null=True, blank=True,on_delete=models.SET_DEFAULT)
machine_brand = models.CharField(max_length=200, null=False)
machine_model = models.CharField(max_length=200, default='')
machine_picture = models.URLField(max_length=200, null=True)
tools = models.ManyToManyField('Tool', default=None, blank=True)
clustered_tags = JSONField(null=True)
elasticsearch_tags = JSONField(null=True, blank=True, default=DEFAULT_TAG_MAP)
machine_slug = models.SlugField()
With the following serializer.
class MachineSerializer(serializers.ModelSerializer):
class Meta:
model = Machine
fields = '__all__'
In my views, I am filtering the data on the company the logged in users belongs to. Now, I want to serialize the object and return it to the client. However, I don't know beforehand whether the queryset is a list of objects or a single object so that I can set the many flag of the serializer to true or false.
#api_view(['GET','POST'])
def manage_operators(request):
user_machines = Machine.objects.filter(company=request.user.company)
user_machines_ser = MachineSerializer(user_machines, many=True)
return Response({'machines': user_machines_ser.data})
Is there any elegant way to solve this? I could solve it this way but there must be a better way of doing it.
if len(user_machines) > 0 :
user_machine_ser = MachineSerializer(user_machines, many=True)
else:
user_machine_ser = MachineSerializer(user_machines, many=False)
Any input much appreciated!
Since you are fetching a QuerySet every time, you don't have to set many=False if there is only one item in the QuerySet.
So you can safely use
user_machine_ser = MachineSerializer(user_machines, many=True)
everytime, no matter how many objects are in the QuerySet.
Since you are passing a QuerySet, you can use the count() [Django doc] method in the __init__() method of MachineSerializer by overriding it.
class MachineSerializer(serializers.ModelSerializer):
class Meta:
model = Machine
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs['instance'].count() > 1: # count() method used here <<<<<
kwargs['many'] = True
else:
kwargs['many'] = False