I'm using pytest.
Going through the tests with a debugger, running both test_joining_previously_entered_queue_returns_previous_queue_details and test_joining_queue_enters_correct_position together, test_joining_previously_entered_queue_returns_previous_queue_details succeeds but test_joining_queue_enters_correct_position fails at queue = get_object_or_404(Queue, pk=kwargs["pk"]) in the JoinQueue view, where the view throws a 404
If I run test_joining_previously_entered_queue_returns_previous_queue_details individually, then the test will pass no problem.
What should happen is that create_company_one creates a Company object, which in turn creates a Queue object (shown in the overridden save method of Company). The associated Queue object does not seem to be created if test_joining_queue_enters_correct_position is run with the other test, but works if the test is run standalone
Why does this happen and how can I fix it?
test_join_queue.py
def create_company_one():
return Company.objects.create(name='kfc')
#pytest.mark.django_db
def test_joining_previously_entered_queue_returns_previous_queue_details(authenticated_client: APIClient):
create_company_one()
url = reverse('user-queue-join', args=[1])
first_response = authenticated_client.put(url)
first_data = first_response.data
second_response = authenticated_client.put(url)
second_data = second_response.data
assert first_data == second_data
#pytest.mark.django_db
def test_joining_queue_enters_correct_position(factory: APIRequestFactory):
"""
Makes sure that whenever a user joins a queue, their position is sorted correctly
based on WHEN they joined. Since all users in self.users join the queue
in a time canonical manner, we can iterate over them to test for their queue positions.
"""
create_company_one()
users = create_users()
view = JoinQueue.as_view()
url = reverse('user-queue-join',args=[1])
request = factory.put(url)
for queue_position, user in enumerate(users):
force_authenticate(request, user=user)
response = view(request, pk=1)
assert response.data["position"] == queue_position + 1
views.py
class JoinQueue(APIView):
permission_classes = [IsAuthenticated]
def put(self, request, *args, **kwargs):
# throws 404 here
queue = get_object_or_404(Queue, pk=kwargs["pk"])
user = request.user
try:
queue_details_of_a_user = user.queue_details.get(queue=queue)
except ObjectDoesNotExist:
queue_details_of_a_user = QueueDetails.objects.create(user=user, queue=queue)
serializer = QueueDetailsSerializer(queue_details_of_a_user)
return Response(serializer.data)
else:
serializer = QueueDetailsSerializer(queue_details_of_a_user)
return Response(serializer.data)
models.py
class Company(models.Model):
name = models.CharField(max_length=15, unique=True)
def save(self, *args: Any, **kwargs: Any) -> None:
created = bool(self.pk)
super().save(*args, **kwargs)
if not created:
Queue.objects.create(company=self)
logging.info(f"{self.name} has created its queue")
return None
def __str__(self) -> str:
return self.name
class Queue(models.Model):
company = models.OneToOneField(
Company, on_delete=models.CASCADE, related_name="queue"
)
users = models.ManyToManyField(User, through="QueueDetails", related_name="queues")
#property
def length(self) -> int:
return len(self.users.all())
#property
def sorted_users(self) -> "QuerySet[User]":
return self.users.all().order_by("queue_details__joined_at")
def __str__(self) -> str:
return f"{self.company}'s queue"
class QueueDetails(models.Model):
joined_at = models.DateTimeField(auto_now_add=True)
queue = models.ForeignKey(
Queue,
related_name="queue_details",
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
related_name="queue_details",
on_delete=models.CASCADE,
)
#property
def position(self) -> int:
for index, user in enumerate(self.queue.sorted_users):
if user == self.user:
return index + 1
raise ValueError("User is not in the queue, Invalid queue position")
Related
I am getting error AttributeError: 'Response' object has no attribute 'user' for the below code I have written
I am trying to get the user info from the context and create a notification model. I am getting the above error while returning the statement. I don't understand why I am getting this error
Model
class CourseNotification(models.Model):
uid = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
unique=True)
course = models.ForeignKey('Course.Course', on_delete=models.SET_NULL, null=True)
user = models.ManyToManyField('Profile.myUser',null=True)
def get_user(self):
return [i for i in self.user.all()]
def __str__(self):
return self.course.course_title
View
class CourseNotificationView(ModelViewSet):
queryset = CourseNotification.objects.all()
serializer_class = CourseNotificationSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
if self.request.user.email is not None:
profile = myUser.objects.get(email=self.request.user.email)
if profile is not None:
notification = CourseNotification.objects.filter(user=profile)
return notification
else:
return Response(data={"User": "Unauthorized User"}, status=HTTP_401_UNAUTHORIZED)
def retrieve(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(data=serializer.data)
Serializer
class CourseNotificationSerializer(serializers.ModelSerializer):
class Meta:
model = CourseNotification
fields = '__all__'
def create(self, validated_data):
users = self.context['request'].user
subject = validated_data['course']
if users is None and subject is None or subject == "":
raise serializers.ValidationError({"Invalid": "Subject could not be Invalid"})
checkNotification = self.checkNotification(users, subject)
if checkNotification is not None and checkNotification.status_code == 200:
return checkNotification
validate_subject = self.validateSubject(users, subject)
if validate_subject.status_code == 200:
return validate_subject
get_data = CourseNotification.objects.create(course=subject)
get_data.user.add(users)
get_data.save()
return Response(data=get_data, status=HTTP_201_CREATED, content_type="application/json")
#staticmethod
def checkNotification(users, subject):
get_data = CourseNotification.objects.filter(user=users, course=subject)
if get_data:
for data in get_data:
data.user.remove(users)
data.save()
return Response(data=get_data, status=HTTP_200_OK, content_type="application/json")
#staticmethod
def validateSubject(users, subject):
get_data = CourseNotification.objects.filter(course=subject).exclude(user=users)
if get_data:
subject = CourseNotification.objects.get(course=subject)
subject.user.add(users)
subject.save()
return Response(data=get_data, status=HTTP_200_OK, content_type="application/json")
I am trying to add data to the model through API. I am facing the problem
I am trying to create chat room instance in database. I need to catch an error when room with two similar users is creating.
Here is my chat-room models.py:
class UserRoom(models.Model):
class Meta:
unique_together = ('user_one', 'user_two')
room_id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user_one = models.ForeignKey('user.User', models.CASCADE, related_name='rooms_one')
user_two = models.ForeignKey('user.User', models.CASCADE, related_name='rooms_two')
def __str__(self):
return f'{self.user_one} | {self.user_two} - {self.room_id}'
serializer.py:
def create(self, validated_data):
user_one = self.context.get('request').user
user_two = validated_data['user_two']
room = UserRoom.objects.create(user_one=user_one, user_two=user_two)
message = ChatMessage.objects.create(
chat_room=room,
sender=user_one,
text=validated_data['message']
)
return room
views.py:
#action(methods=['POST'], detail=False)
def create_chat(self, request):
serializer = CreateChatSerializer(data=request.data,
context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
a = serializer.save()
return Response(
UserRoomsSerializer(a, context=self.get_serializer_context()).data
)
Also, I need an exception of creating existing room.
you can use .exists() to check if the room exists in the db before calling create
if the room exists you raise a validation exception
for example
serializer.py:
def create(self, validated_data):
user_one = self.context.get('request').user
user_two = validated_data['user_two']
if UserRoom.objects.filter(user_one=user_one, user_two=user_two).exists():
raise serializers.ValidationError("room name is not unique")
room = UserRoom.objects.create(user_one=user_one, user_two=user_two)
message = ChatMessage.objects.create(
chat_room=room,
sender=user_one,
text=validated_data['message']
)
return room
and in your views.py check for validation errors
#action(methods=['POST'], detail=False)
def create_chat(self, request):
serializer = CreateChatSerializer(data=request.data,
context=self.get_serializer_context())
if serializer.is_valid():
a = serializer.save()
return Response(
UserRoomsSerializer(a, context=self.get_serializer_context()).data
)
else:
return Response(serializer.errors)
I am trying to call a queryset for a model to add to my serializer using objects.all() but the debug said Unable to set repr for <class 'django.db.models.query.Queryset'>
Here is my viewset
class TransactionReceiptViewSet(viewsets.GenericViewSet,
viewsets.mixins.RetrieveModelMixin,
viewsets.mixins.ListModelMixin):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = base_serializers.TransactionReceiptSerializer
queryset = models.TransactionReceipt.objects.all()
def get_queryset(self):
user = self.request.user
return models.TransactionReceipt.objects.filter(user_profile=user)
def retrieve(self, request, *args, **kwargs):
response = super(TransactionReceiptViewSet, self).retrieve(request, *args, **kwargs)
receipt = self.get_object()
serializer = self.get_serializer(receipt)
product_qs = models.ProductReceipt.objects.all()
products_data = base_serializers.ProductReceiptSerializer(
product_qs, many=True)
serializer.data['products'] = products_data
return Response(serializer.data)
and here is the model I tried to call for
class ProductReceipt(models.Model):
id = models.AutoField(primary_key=True)
amount = models.IntegerField(default=1)
product = models.ForeignKey(Product, on_delete=models.DO_NOTHING, default=None)
created_date = models.DateTimeField('Date of purchase', auto_now=True)
transaction_receipt = models.ForeignKey(TransactionReceipt, on_delete=models.CASCADE)
price = models.IntegerField(default=0)
def __str__(self):
return "object created"
def __init__(self):
super().__init__()
self.product = Product()
self.transaction_receipt = TransactionReceipt()
def save(self, **kwargs):
self.amount = 1
self.created_date = datetime.now()
self.price = self.product.price_tag.price
When I debug the API, it said that Unable to set repr for <class 'django.db.models.query.Queryset'> in product_qs and nothing is returned
Edit:
I think that the Model have to do something with this. I have tried to create a ModelViewSet for ProductReceipt and it worked fine. But when i try to make the query manually. it somehow broke the mapping to the foreign key??? and return nothing?
Okey lets check a couple of things. First of all in your ProductReceipt class the def save(self, **kwargs) method is not calling super and that's a huge problem because the objects are not gonna be saved ever. Secondly, in the ProductReceipt class the def __init__(self) method you are assigning a new Product and a new TransactionReceipt to your ProductReceipt instance, but you are not setting the data of this two objects neither saving them in any place (maybe you should assign them inside save method and save them before calling super?).
Try this corrections and if it keeps not working we will another possible errors.
Finally, def __str__(self) is a string representation of your object, it will be a good implementation for example:
def __str__(self):
return self.product.name + ' x' + str(amount)
Turn out that the product field is not set to null=True. And i have old data with that field point to nothing. There for it break when trying to query from the database. In short, i didn't migrate properly.
I have a generic view declared as follows:
class CustomerDelete(LoginRequiredMixin,DeleteView):
model = models.Customer
success_url = reverse_lazy('customer-list')
And a model declared as follows:
class Order(models.Model):
Customer = models.ForeignKey(Customer, on_delete=models.PROTECT, default=None)
Shop = models.ForeignKey(Shop, on_delete=models.PROTECT, default=None)
Status = models.IntegerField(choices=STATUS);
Reference = models.CharField(max_length=50)
Date = models.DateTimeField(default=None)
LastAuthorizationDate = models.DateTimeField(default=None, null=True)
LastUpdated = models.DateTimeField(auto_now=True)
def get_absolute_url(self):
return reverse_lazy('order-view', None, [self.id])
def type(self):
return 'Order'
def Name(self):
return self.Customer.Name + ' - ' + self.Shop.Name + ' - ' + self.Reference
Upon delete I get the following exception:
ProtectedError at /customer/2/delete/ ("Cannot delete some instances
of model 'Customer' because they are referenced through a protected
foreign key: 'Order.Customer'", ,
, , , ]>)
What would be the best class method to override and catch the exception that would allow me to redirect to the referrer with an error attached?
Thanks in advance.
You need to override the delete method, to add your custom logic:
class CustomerDelete(LoginRequiredMixin,DeleteView):
model = models.Customer
success_url = reverse_lazy('customer-list')
error_url = reverse_lazy('customer-has-orders-error')
def get_error_url(self):
if self.error_url:
return self.error_url.format(**self.object.__dict__)
else:
raise ImproperlyConfigured(
"No error URL to redirect to. Provide a error_url.")
def delete(self, request, *args, **kwargs):
"""
Call the delete() method on the fetched object and then redirect.
"""
self.object = self.get_object()
success_url = self.get_success_url()
error_url = self.get_error_url()
try:
self.object.delete()
return HttpResponseRedirect(success_url)
except models.ProtectedError:
return HttpResponseRedirect(error_url)
If you are going to be using this often, you can create your own custom mixin with the above logic.
In addition, consider implementing a soft delete in your application, so that records are not deleted from the database immediately, but are flagged for deletion at a later date - once they are archived. Otherwise you risk having issues with your business logic.
I have a couple of models and am a little confused about how to create all of the associations. I have profiles, events, messages, and inboxes. Each profile has an eventList that holds events. Each message is associated with an event too. Each inbox is associated with a profile and multiple messages. What I want to do is, whenever a message object is created, for it to be inserted into the inbox of every user who holds the event that message is associated with in their eventList. Providing my models and the view that I'm writing:
class Profile(models.Model):
user = models.OneToOneField(User)
name = models.CharField(max_length=50)
eventList = models.ManyToManyField(Event, blank="TRUE", null="TRUE", related_name='event_set+')
ownedEvent = models.ManyToManyField(Event, blank="TRUE", null="TRUE", related_name='owned_set')
def __unicode__(self):
return self.name
class inbox(models.Model):
def __unicode__(self):
return self.user.name
user = models.OneToOneField(Profile)
message = models.ManyToManyField(message, blank="TRUE", null="TRUE")
read = models.BooleanField(default = 0)
class message(models.Model):
def __unicode__(self):
return unicode(self.body)
def save(self, *args, **kwargs):
if not self.id:
self.created = datetime.datetime.today()
super(message, self).save(*args, **kwargs)
body = models.CharField(max_length=250)
eid = models.ForeignKey(Event)
#login_required
def sendMail(request):
event_id = request.POST['event_id']
e = Event.objects.get(id = event_id)
text = request.POST['body']
m = message(eid = e, body = text)
m.save()
users = e.eventList_set.all()
return HttpResponse(status = 200)
If you want this to always happen you can put the relevant code in the message.save() method or a post-save signal handler. If it's just for this view then you can put the code there. In either case, this should work:
# msg is the message instance
for box in inbox.objects.filter(user__eventList=msg.eid):
box.message.add(msg)