how to handle concurrency of bookmark system in django? - django

I tried to implement bookmark system for product,
when users click bookmark button, it will be recorded in his bookmark table,
and update bookmark count field in Product Model. However I faced DB Lock
when there is too many request at the same time. Also, I realized that when users
add or delete bookmark at the same time, there will be concurency issues like,
users can not read Product Information or Bookmark count or DB Lock..
How to handle concurrency in my situation? I know the exclusive lock but
it will lower the performance.. please help me..
here are my codes
class Bookmark(models.Model):
_id = models.AutoField(primary_key=True, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='bookmark_user')
def __str__(self):
return str(self.user)
class BookmarkItems(models.Model):
_id = models.AutoField(primary_key=True, editable=False)
user = models.CharField(max_length=255, null=True, blank=True)
image = models.CharField(max_length=255, null=True, blank=True)
bookmark = models.ForeignKey(Bookmark, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def image_preview(self):
if self.image:
return mark_safe('<img src="{0}" width="75" height="75" />'.format(self.image))
else:
return '(No image)'
def __str__(self):
return str(self.product)
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def addBookmark(request):
user = request.user
user_id = request.data['user']
product_id = request.data['product_id']
image = request.data['image']
product = Product.objects.get(_id=product_id)
with transaction.atomic():
bookmark = Bookmark.objects.get_or_create(user=user)
Product.objects.filter(_id=product_id).update(
bookmark_count = F('bookmark_count') + 1
)
BookmarkItems.objects.create(
user = user_id,
image = image,
bookmark=bookmark[0],
product=product,
)
return Response({'success':'The bookmark has been created.'})
#api_view(['DELETE'])
#permission_classes([IsAuthenticated])
def deleteBookmark(request, pk):
user =request.user.id
with transaction.atomic():
Product.objects.filter(_id=pk).update(
bookmark_count = F('bookmark_count') - 1
)
BookmarkItems.objects.filter(user=user, product=pk).delete()
return Response({'success': 'The bookmark has been deleted.'})

Related

How to test methods in models.py related to filters in django using pytest?

I have models which contains lots of classmethods for filtering different kinds of data. Now the problem is, these classmethods are called in views for several functionality. For example, I have Order table as shown below:
class Order(models.Model):
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
total = models.DecimalField(max_digits=12, decimal_places=2, default=0)
order_date = models.DateTimeField(auto_now_add=True)
cancelled = models.BooleanField(default=False)
items = models.ManyToManyField(Items, through='OrderItems')
status = models.CharField(max_length=30, choices=choice_status, default='waiting')
restaurant = models.ForeignKey(Restaurant, on_delete=models.SET_NULL, related_name='orders', null=True)
razorpay_payment_id = models.CharField(max_length=100, null=True, blank=True)
razorpay_order_id = models.CharField(max_length=100, null=True, blank=True)
mode = models.CharField(max_length=10, choices=choice_mode, default='COD', null=True, blank=True)
paid = models.BooleanField(default=False, null=True, blank=True)
delivery_charges = models.DecimalField(max_digits=12, decimal_places=2, default=0)
user_address = models.TextField(max_length=500)
user_address_lat = models.DecimalField(max_digits=9, decimal_places=6)
user_address_long = models.DecimalField(max_digits=9, decimal_places=6)
restaurant_address = models.TextField(max_length=500)
restaurant_address_lat = models.DecimalField(max_digits=9, decimal_places=6)
restaurant_address_long = models.DecimalField(max_digits=9, decimal_places=6)
#classmethod
def get_order_with_search_params(cls, params, queryset):
search_order_id = None
with contextlib.suppress(ValueError):
search_order_id = int(params)
return queryset.filter(
Q(user__username__icontains=params) |
Q(restaurant__name__icontains=params) |
Q(id=search_order_id)
)
#classmethod
def get_order_from_status(cls, params, queryset):
return queryset.filter(Q(status=params.lower()))
#classmethod
def get_order_from_restaurant_id(cls, params, queryset):
restaurant_id = None
with contextlib.suppress(ValueError):
restaurant_id = int(params)
return queryset.filter(Q(restaurant_id=restaurant_id))
#classmethod
def get_object_from_pk(cls, pk):
return get_object_or_404(cls, pk=pk)
#classmethod
def get_total_of_all_orders(cls, queryset=None):
if not queryset:
queryset = cls.objects.all()
return queryset.filter(status='delivered').aggregate(total_sum=Sum('total'))['total_sum']
Now to test these functionality, I have to first have to do following:
register user (restaurant type)
activate this user
add Items to store with restaurant user
register another user (customer type)
activate this user too
add item to cart
place order
After placing order, it will create Order object, then I can test functionalities like filter. So this is integration testing.
Also along this steps, many related tables are affected like while registering users Address table will have been created, while adding items its CartItem table is populated, and many other things. This is the reason why I cannot just use Order.objects.create(**kwargs) and then test classmethods of model. Should I skip testing these classmethods? or their is any other ways to test these methods?
For ref.: I have only created testcases for fields and str methods shown below
def test_str_method(self):
obj = State.objects.create(name='TestState')
assert str(obj) == obj.name
def test_document_data(self, get_agent_document_form_initial_data):
agent = User.objects.create(
**{'username': 'test_agent1', 'mobile_number': '+917894541211', 'email': 'test_agent1#gmail.com'})
document_obj = Document.objects.create(agent=agent,
**get_agent_document_form_initial_data(pancard_document=filename,
license_document=filename))
assert document_obj.agent == agent
# assert document_obj.id == 1
assert document_obj.is_verified is False
assert document_obj.account_no == 1234567890
assert document_obj.ifsc_code == 'ABCD0123456'
assert document_obj.pancard_number == 'BNZAA2318J'
assert document_obj.ifsc_code == 'ABCD0123456'
assert document_obj.license_number == 'HR-0619850034761'
assert document_obj.pancard_document == './media/default.jpg'
assert document_obj.license_document == './media/default.jpg'
assert document_obj.razorpay_contact_id is None
assert document_obj.razorpay_fund_account_id is None
assert str(document_obj) == f'{document_obj.id} | {document_obj.agent} | {document_obj.is_verified}'
Just checking if values of fields are correct or not. But I want to write testcases for other methods in models which is related to flow of project.
You can prepopulate data in Database, For better understanding, you can follow this link.
And then simply test your method shown below:
class TestOrderModelMethods:
def test_get_order_from_status(self):
assertQuerysetEqual(
Order.get_order_from_status(params='waiting', queryset=Order.objects.all()),
Order.objects.filter(status='waiting'),
ordered=False)

How can I count how many products are ordered by a single user?

This is a single-page website that is kinda ecommerce site. I just want to count a single user how many products are ordered. How can I count?
index view:
def index(request):
total_user = User.objects.count()-1
total_orders =Frontend_Order.objects.count()
context = {
"total_user":total_user,
"total_orders":total_orders
}
return render(request,'0_index.html',context)
frontend_view:
def frontend_orders(request):
if request.user.is_authenticated:
if request.method == "POST":
frontend_orders_request = request.FILES['frontend_file'] if 'frontend_file' in request.FILES else None
sections = request.POST.get('numField11')
functionality = request.POST.get('frontend')
if functionality == "Portfolio":
price = int(sections)*10
elif (functionality == "e-Commerce") or (functionality == "social-media"):
price = int(sections)*15
Frontend_Order.objects.create(
files = frontend_orders_request,
Number_of_Section = sections,
Website_Functionality = functionality,
Price = price, USer = request.user,
Email = request.user.email
)
messages.success(request,f"{request.user.first_name}, Your order is procecing. I'll cotact you before placing the order.")
return redirect("/", userz = request.user)
else:
messages.error(request,"Please login or create an account.")
return redirect("/")
Frontend Order Model:
class Frontend_Order(models.Model):
USer = models.CharField(max_length=50, null=True)
Price = models.CharField(max_length=250, null=True)
Number_of_Section = models.CharField(max_length=250, null=True)
Website_Functionality = models.CharField(max_length=1, null=True)
Email = models.EmailField(max_length=50, null=True)
files = models.FileField(upload_to="new_file/", null=True, blank=True)
def __str__(self):
return str(self.pk)+ str(".") + str(self.USer)
Yes, it's possible. You can get the current user from the request object and use the filter method, like so:
def index(request):
total_user = User.objects.count()-1
total_orders =Frontend_Order.objects.filter(user=request.user).count()
context = {
"total_user":total_user,
"total_orders":total_orders
}
return render(request,'0_index.html',context)
However, this will only work if you connect your users to your orders, with a foreign key, instead of a CharField:
class Frontend_Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
# etc...
You should use Frontend_Order.objects.filter(USer=userid) (or Frontend_Order.objects.filter(USer.id=userid) if it didn't worked) to get user orders by it's user id and then, use a for loop and a counter variable to iterate on filtered orders and add their products count to the counter.
It is better to reconsider your chosen names.

Restrict users to read exclusively one's own articles

I have the following article_detail view which restrict that only users logged in could read the details.
#login_required(login_url="/user/login/")
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk)
total_views = r.incr("article:{}:views".format(article.id))
page_number = request.GET.get('page_number', 1)
#mimic SO's 100 per-page
per_page = request.GET.get("per_page", 30)
....
How could I set that each user can exclusively read contents posted by oneself?
The article model
class Article(models.Model):
STATUS = (
(1, 'normal'),
(0, 'deleted'),
)
tags = models.ManyToManyField(Tag, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
block = models.ForeignKey(Block, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField() # set the widget
status = models.IntegerField(choices=STATUS, default=1)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ("-date_created",)
def __str__(self):
return self.title
Just add one more criteria (owner) in your query:
article = get_object_or_404(Article, pk=pk, owner=request.user)
If the request user is NOT the article owner, then 404 will be returned.
You can also return 403 permission denied error with following codes which is more meaningful to the client:
from from django.http.response import HttpResponseForbidden
article = get_object_or_404(Article, pk=pk)
if article.owner.pk != request.user.pk:
return HttpResponseForbidden()
... # permission checking pass, so other code goes here

Django FOREIGN KEY constraint failed

My app is a shopping cart. I have a function in views.py that is triggered when someone adds an item to the cart. The function checks if the user has an active order (one that is in the cart but hasn't been paid for).
But the code fails at `user_order, status = Order.objects.get_or_create(owner=user_profile, is_ordered=False)
#login_required()
def add_to_cart(request, **kwargs):
#get the user profile
user_profile = get_object_or_404(UserProfile, user=request.user)
#filter products by id
producto = Producto.objects.filter(id=kwargs.get("pk", "")).first()
#create OrderItem, of the selected product
order_item, status = OrderItem.objects.get_or_create(producto=producto)
if status == False: #adds one to the order_item
order_item.quantity += 1
order_item.save()
print(order_item.quantity)
#create order associated with the user
user_order, status = Order.objects.get_or_create(owner=user_profile, is_ordered=False)
user_order.items.add(order_item)
#print(user_order.items.all()) #queries the items
#print(user_order)
#date is not beign added
# generate a reference code
user_order.ref_code =random.randint(0,100000)
user_order.save()
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
Relevant models:
class Order(models.Model):
fecha_reparto = models.OneToOneField(DiasDeReparto, on_delete=models.CASCADE, default= 1)
order_nodo = models.OneToOneField(Nodo, on_delete=models.CASCADE, default= 1)
ref_code = models.CharField(max_length=15)
owner = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
is_ordered = models.BooleanField(default=False)
items = models.ManyToManyField(OrderItem)
date_ordered = models.DateTimeField(null=True)
def get_cart_items(self):
return self.items.all()
def get_cart_total(self):
return sum([item.producto.precio * item.quantity for item in self.items.all()])
def __str__(self):
return self.ref_code
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class OrderItem(models.Model):
producto = models.OneToOneField(Producto, on_delete=models.SET_NULL, null = True)
quantity = models.IntegerField(default=1)
is_ordered = models.BooleanField(default=False)
date_added = models.DateTimeField(auto_now = True)
date_ordered = models.DateTimeField(null=True)
def __str__(self):
return self.producto.nombre
The problem can be solved with something like this:
try:
Order.objects.get(owner=user_profile)
except:
Order.objects.create(fecha_reparto=dia_reparto[0], order_nodo=nodo[0], ref_code="asdas", owner=user_profile, is_ordered=False, date_ordered=datetime.datetime.now())
But I don't get why get_or_create doesn't work
On your Order model you have a few related model fields and in the get_or_create you are not specifying any defaults. The get or create needs the required values to input if creating and you have a few related fields which are required. That will also explain why the try and except works because you add the fields when creating in the except.
defaults = {fecha_reparto: dia_reparto[0], order_nodo: nodo[0]......}
user_order, status = Order.objects.get_or_create(owner=user_profile, is_ordered=False, defaults=defaults)
Use id instead model instace in this line:
user_order, status = Order.objects.get_or_create(owner=user_profile, is_ordered=False)
So it should look like this:
user_order, status = Order.objects.get_or_create(owner_id=user_profile.pk, is_ordered=False)
It worked for me with similar problem.

Django admin does not show all entities

I've inherited an app created with Django. There is a problem with it: in admin interface, the page lists not all entities (videos), but some (16 of 25). I have no idea, what is this.
Then I run python manage.py shell, and there Video.objects.all(), there are all 25 objects (counted them using len and by iterating them with for loop).
I have found no managers or whatever (maybe I just don't know where to look for them).
On the bottom of admin page: 25 videos, while there are only 16 rows.
Then I add to VideoModelAdmin class list_per_page = 10, paginator show three pages, but only first two of them has any Videos, third shows no rows.
Here are some code.
# admin.py
class VideoModelAdmin(admin.ModelAdmin):
list_display = ['title', 'short_desc', 'author', 'redactor_choise', 'views_num', 'rating', 'is_published']
list_filter = ['is_published', 'redactor_choise']
list_per_page = 10
actions = ['make_published', 'update_comments_count']
exclude = ('file_lq', 'file_hq', )#'thumb',)
def make_published(self, request, queryset):
queryset.update(is_published=1)
make_published.short_description = "Опубликовать выделенные"
def save_model(self, request, obj, form, change):
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return instance
def update_comments_count(self, request, queryset):
for video in queryset:
video.update_comments_count()
update_comments_count.short_description = "Пересчитать комментарии!"
# later there
admin.site.register(Video, VideoModelAdmin)
# models.py
class Video(models.Model):
def make_upload_path(instance, filename):
return 'video/thumbs/' + generate_random_name(filename)
category = models.ManyToManyField(Category, limit_choices_to = {'is_published': 1})
title = models.CharField(max_length=128)
short_desc = models.CharField(max_length=255)
long_desc = tinymce_models.HTMLField(blank=True)
file_lq = models.FileField(upload_to='video/lq/', null=True, blank=True)
file_hq = models.FileField(upload_to='video/hq/', null=True, blank=True)
thumb = models.FileField(upload_to=make_upload_path, blank=True, null=True)
#thumb = fields.ThumbnailField(upload_to=make_upload_path, sizes=settings.VIDEO_THUMB_SIZE, blank=True, null=True)
author = models.ForeignKey(User, editable=False)
redactor_choise = models.BooleanField(default=False)
views_num = models.SmallIntegerField(default=0, editable=False)
comments_num = models.SmallIntegerField(default=0, editable=False)
rating = models.SmallIntegerField(default=0, editable=False)
voters = fields.PickledObjectField(blank=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
def get_absolute_url(self):
return "/video/%d" % self.id
def views_num_plus(self):
cursor = connection.cursor()
cursor.execute('update soctv_video set views_num=views_num+1 where id=%d', [self.id])
cursor.close()
def update_comments_count(self):
from threadedcomments.models import ThreadedComment as Comment
self.comments_num = Comment.objects.filter(video=self).count()
self.save()
#cursor = connection.cursor()
#cursor.execute('update soctv_video set comments_num = (select count(*) from soctv_comment where video_id = %s) where id = %s', [self.id, self.id])
#cursor.close()
def update_categories_counts(self):
cursor = connection.cursor()
cursor.execute('update soctv_category set num_items = (select count(*) from soctv_video_category where category_id = soctv_category.id)')
cursor.close()
def is_user_voted(self, uid):
try:
if self.voters[uid]:
return self.voters[uid]
except Exception:
return False
def increment_view_count(self, token):
import md5
token = md5.new(token).hexdigest()
if VideoView.objects.filter(uniquetoken=token).count() == 0:
VideoView(uniquetoken = token, video = self).save()
def view_count(self):
return self.views_num + VideoView.objects.filter(video=self).count()
def __unicode__(self):
return unicode(self.title)
The problem can be that some FK in some of your videos points to something that does not exist.
I had the same problem and this was the reason.
Django will silently fail if the value is not there in the foreign key column.
Add both null and blank attribute to the column
null=True, blank=True
Make sure that you are logged in to the correct account aswell.
In my case, My account did not have permissions to modify <Model>