how to allow users to rate a product only once, - django

a Reviews model linked to Users and Product, nothing special,
users can vote as much as they want which is working,
but i'm stuck restricting the rating to just once
the view so far:
class ReviewCreateView(CreateView):
model = Review
form_class = ReviewForm
template_name = "form.html"
success_url = reverse_lazy('product_list')
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
model
class Review(models.Model):
product = models.ForeignKey(Product, on_delete=models.PROTECT, null=True,related_name='reviews')
user = models.ForeignKey(User, on_delete=models.PROTECT, null=True,related_name='reviewers')
rating = models.IntegerField(null=True, blank=True, default=0)
comment = models.TextField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
the other models wouldn't help that muc ,Product just describe a product ,and default (regular) User model
the form is ModelForm based on the Review Model itself
any suggestion is welcome, even if i have to redo the model, thank you very much

set db constraint like so:
class Meta:
constraints = [
UniqueConstraint([fields=['user', 'product'], name='product_user_unique'),
]
the condition for your views would be like this:
def get(self, request, product):
if (Review.objects.filter(user=request.user)
.filter(product_id=product).exists():
# probably, you'd want to pass some
# conditional data to the template's context here
do_something()
That could be it. You restrict non-unique reviews by UI, but if it's somehow bypassed, user will get a server error. Of course, you could also apply validation in your CreateView (or analogous).

From architectural standpoint, you can always create a review_user table which will have the Product ID, and the ID of the users who voted on that product.. and upon second vote, you check if there's already a vote in the table.
Once you have this understanding, I am sure that you will find a way to incorporate the logic within your code.

Related

django owner permission in detail view

Here is my code
class EnrollmentDetail(generic.DetailView):
model = Enrollment
template_name = 'enrollments/enrollment_detail.html'
context_object_name = 'enrollment
Simple djeneric django detail view. I want only the owner of this view to can access it. Every one else i don't want go get it. I want to restrict their access. I think to raise error in that case but don't know which one. It is frustrating, because it is not complicated problem but can't solve it. I checked many posts on that matter but none of them offer slick and clean solution ( decorators, supers(), querysets etc.)
Here is my model
class Enrollment(models.Model):
"""Defines Enrollment model"""
doctor_name = models.ForeignKey(DoctorProfile, on_delete=models.CASCADE)
patient_name = models.CharField(max_length=30)
symptoms = models.CharField(max_length=80)
diagnosis = models.CharField(max_length=30)
received_at = models.DateTimeField(auto_now=True)
room_number = models.PositiveIntegerField()
Thanks for help
maybe you can override the get_queryset in your Detailview,
def get_queryset(self):
return self.model.objects.filter(user=self.request.user)
Simple make a if statement and check if the logged-in user has superuser rights:
class EnrollmentDetail(generic.DetailView):
if request.user.is_superuser:
model = Enrollment
template_name = 'enrollments/enrollment_detail.html'
context_object_name = 'enrollment
else:
# redirect to page

How to filter a Django model based on requirement from another model?

I'd like to retrieve all my contacts from my Contact model excluding those listed on my DoNotContact model. Is the following the most efficient way to do that: contacts = Contact.objects.filter(dont_contact=False) Wondering if this is going to take long to process, is there a more efficient way?
class Contact(models.Model):
email = models.CharField(max_length=12)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
audience = models.ForeignKey(Audience, on_delete=models.CASCADE)
def dont_contact(self):
try:
get_object_or_404(DoNotContact, email=self.email)
return True
except:
return False
def __str__(self):
return self.email
class DoNotContact(models.Model):
email = models.CharField(max_length=12)
#views.py
def send_email(request):
if request.method == "POST":
contacts = Contact.objects.filter(dont_contact=False)
Kwargs used model queryset filter methods are resolved into database columns. dont_contact here is a method and doesn't exist as a column in Contact model so calling Contact.objects.filter(dont_contact=False) will raise a FieldError.
For current implementation of your models you can do following
dont_contacts = DoNotContact.objects.values('email')
contacts = Contact.objects.exclude(email__in=dont_contacts)
A better solution with higher performance is to remove DoNotContact and add a BooleanField to Contact which handles your requirement.

Remove option from generic class view in Django

I just started learning Django this week and I'm trying to figure out how I can remove an option from a select menu being rendered in a class based view. The dropdown is for a Foreign Key field that links to my users table.
The functionality here is that I do not want the current user logged into show up on that list (basically I don't want someone to be able to select themself). How can I go about doing this?
View:
class TransferCreateView(CreateView):
model = Transfer
template_name = 'points/transfer_form.html'
fields = ['receiver', 'message', 'amount']
Model:
class Transfer(models.Model):
receiver = models.ForeignKey(User, null=False,
on_delete=models.CASCADE, related_name='receiver')
sender = models.ForeignKey(User, null=False, on_delete=models.CASCADE, related_name='sender')
amount = models.IntegerField(
validators=[
MinValueValidator(1),
MaxValueValidator(1000)],
null=False)
message = models.CharField(max_length=100)
date_sent = models.DateTimeField(default=timezone.now)
Basically, I don't want the person who is the sender (which i was going to set in the code in a form_valid() function) to be an option for 'receiver' in the template when it renders.
Override the get_form method of the CreateView and change the queryset of that field, something like this:
class TransferCreateView(CreateView):
model = Transfer
template_name = 'points/transfer_form.html'
fields = ['receiver', 'message', 'amount']
def get_form(self, form_class):
form = super().get_form(form_class)
form.fields['receiver'].queryset = User.objects.exclude(id=self.request.user.id)
return form

M2M using through and form with multiple checkboxes

I'd like to create a form allowing me to assign services to supplier from these models. There is no M2M relationship defined since I use a DB used by others program, so it seems not possible to change it. I might be wrong with that too.
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
contact = models.ForeignKey(Contact, null=False, blank=False)
class SupplierPrice(models.Model):
service_user = models.ForeignKey('ServiceUser')
price_type = models.IntegerField(choices=PRICE_TYPES)
price = models.DecimalField(max_digits=10, decimal_places=4)
I've created this form:
class SupplierServiceForm(ModelForm):
class Meta:
services = ModelMultipleChoiceField(queryset=Service.objects.all())
model = ServiceUser
widgets = {
'service': CheckboxSelectMultiple(),
'contact': HiddenInput(),
}
Here is the view I started to work on without any success:
class SupplierServiceUpdateView(FormActionMixin, TemplateView):
def get_context_data(self, **kwargs):
supplier = Contact.objects.get(pk=self.kwargs.get('pk'))
service_user = ServiceUser.objects.filter(contact=supplier)
form = SupplierServiceForm(instance=service_user)
return {'form': form}
I have the feeling that something is wrong in the way I'm trying to do it. I have a correct form displayed but it is not instantiated with the contact and checkboxes aren't checked even if a supplier has already some entries in service_user.
You are defining services inside your Meta class. Put it outside, right after the beginning of SupplierServiceForm. At the very least it should show up then.
Edit:
I misunderstood your objective. It seems you want to show a multiple select for a field that can only have 1 value. Your service field will not be able to store the multiple services.
So, by definition, your ServiceUser can have only one Service.
If you don't want to modify the database because of other apps using it, you can create another field with a many to many relationship to Service. That could cause conflicts with other parts of your apps using the old field, but without modifying the relationship i don't see another way.
The solution to my problem was indeed to redefine my models in oder to integrate the m2m relationship that was missing, using the through argument. Then I had to adapt a form with a special init method to have all selected services displayed in checkboxes, and a special save() method to save the form using m2m relationship.
class Supplier(Contact):
services = models.ManyToManyField('Service', through='SupplierPrice')
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
supplier = models.ForeignKey(Supplier, null=False, blank=False)
price = models.Decimal(max_digits=10, decimal_places=2, default=0)
And the form, adapted from the very famous post about toppings and pizza stuff.
class SupplierServiceForm(ModelForm):
class Meta:
model = Supplier
fields = ('services',)
widgets = {
'services': CheckboxSelectMultiple(),
'contact_ptr_id': HiddenInput(),
}
services = ModelMultipleChoiceField(queryset=Service.objects.all(), required=False)
def __init__(self, *args, **kwargs):
# Here kwargs should contain an instance of Supplier
if 'instance' in kwargs:
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault('initial', {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data (checked boxes).
initial['services'] = [s.pk for s in kwargs['instance'].services.all()]
ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
supplier = ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
def save_m2m():
new_services = self.cleaned_data['services']
old_services = supplier.services.all()
for service in old_services:
if service not in new_services:
service.delete()
for service in new_services:
if service not in old_services:
SupplierPrice.objects.create(supplier=supplier, service=service)
self.save_m2m = save_m2m
# Do we need to save all changes now?
if commit:
self.save_m2m()
return supplier
This changed my first models and will make a mess in my old DB but at least it works.

Django admin site for limited privilege users

I have a Django application, and have two apps within the project: accounts and store. In my store app, I have Product, Category model and other models.
I have admin site, from where I can register Products. In the accounts, I allow general people to signup and log in.
Now, I also want users to be able to register Products, so that they can sell their own products. However, I do not want to give them to a complete access to the admin site.
How can I give such limited access to the admin site to general users?
Thanks.
In admin you can make groups and assign access to models you wanted and same could be applied to users but you might be interested in limited access to models records to which logged user have itself added. in order to achieve this you have to define one column in model to foreign key to users like below I have defined model for company and assigned each company to user:
class Company(models.Model):
name = models.CharField(max_length=64, primary_key=True)
kam = models.ForeignKey(User, verbose_name='KAM', blank=True, null=True)
address = models.TextField(blank=True, null=True)
city = models.CharField(max_length=32, blank=True, null=True)
country = models.CharField(max_length=32, blank=True, null=True)
phone = models.CharField(max_length=32, blank=True, null=True)
fax = models.CharField(max_length=32, blank=True, null=True)
url = models.URLField(blank=True, null=True)
class Meta:
verbose_name_plural = 'Companies'
#unique_together = (('name', 'kam'),).
def __unicode__(self):
return self.name
Now your model will be associated with user, Now you could restrict records to be loaded according to logged user using admin.py in modeladmin definition like given below:
def queryset(self, request):
qs = super(CompanyAdmin, self).queryset(request)
# If super-user, show all comments
if request.user.is_superuser:
return qs
return qs.filter(kam=request.user)
thats simple let me know if this is what you want?
Also you could assign read only right. in model admin
Admin uses a class ModelAdmin to render the page as you would probably already know. That class has a queryset method which you override based with a new filter, based on who is accessing the site, as suggested by sharafjaffri.
But that filtering alone isn't sufficient. You also need to filter the values displayed in the dropdowns, to only those created by the user. And then when saved, you should associate the new object with the portal of the user adding it.
Here is my quick untested implementation of the same:
class PortalAdmin(admin.ModelAdmin):
exclude = ('portal',)
def queryset(self, request):
"""
Filter the objects displayed in the change_list to only
display those for the currently signed in user.
"""
qs = super(UserAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(portal=request.user.profile.portal)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
the_model = db_field.related.parent_model
if hasattr(the_model,'portal'):
kwargs['queryset'] = the_model.objects.filter(portal=request.portal)
return super(PortalAdmin,self).formfield_for_foreignkey(db_field, request, **kwargs)
def save_model(self, request, obj, form, change):
if not change:
obj.portal = request.portal
obj.save()