Custom Permissions in Django Rest Framework - django

I have this get View that I want to check IsOwner Permission.
Permission Class
class IsOwnerVendor(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print(f"Vendor Email:{obj.vendor_id.email}")
print(f"Loggon user:{obj.vendor_id.email}" )
return obj.vendor_id.email == request.user
this is my object Model
class Menu(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500)
price = models.FloatField()
quantity = models.IntegerField(default=1)
menu_cat = models.CharField(choices=MENU_CAT, max_length=5)
date_created = models.DateTimeField(auto_now_add=True)
last_edited = models.DateTimeField(auto_now=True)
vendor_id = models.ForeignKey(Vendor, on_delete=models.CASCADE)
is_recurring = models.BooleanField(default=False)
recurring_freq = models.IntegerField(default=1)
Vendor Model
class Vendor(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
business_name = models.CharField(max_length=100)
email = models.EmailField()
phone_number = PhoneField(help_text='Vendor phone number')
registered_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.business_name
and this is my view
class MenuDetailView(generics.GenericAPIView):
permission_classes = [IsOwnerVendor | IsOwnerVendor]
def get_serializer_class(self):
if self.request.method == 'PUT':
return MenuUpdateSerializer
elif self.request.method == 'GET':
return MenuListSerializer
else:
return MenuListSerializer
def get_object(self, pk):
try:
obj = Menu.objects.get(pk=pk, )
self.check_object_permissions(self.request, obj)
return obj
except Menu.DoesNotExist:
raise Http404
#method_permission_classes((IsOwnerVendor,))
def get(self, request, pk, format=None):
my_menu = self.get_object(pk=pk)
menu_serializer = MenuListSerializer(my_menu)
return Response(menu_serializer.data, status=status.HTTP_200_OK)
When I try to access the view, I always get the error below
{
"detail": "You do not have permission to perform this action."
}
I have read the DRF doc and I still cannot pinpoint where my issue lies.
I also printed the Permission checkers on the console and saw that it was supposed to return true.

The main point is you should let has_object_permission method return True, you can try:
return obj.vendor_id.email == request.user.email or
return obj.vendor_id.user == request.user, if this still not work, you can print log or set breakpoint to see why this method return False.Remember to restart you localserver before you next test.

Related

how to pass the Integrity error django and DRF without stoping execution raising the error

I am byulding an API using django and DRF my code is this
models.py
class Company(models.Model):
"""Company object."""
name_company = models.CharField(max_length=255)
symbol = models.CharField(max_length=10, unique=True)
cik = models.CharField(max_length=150, blank=True)
sector = models.CharField(max_length=150, blank=True)
industry_category = models.CharField(max_length=150, blank=True)
company_url = models.TextField(blank=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name_company
views.py
class CompanyViewSet(viewsets.ModelViewSet):
"""View for manage company APIs."""
serializer_class = serializers.CompanyDetailSerializer
queryset = Company.objects.all()
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
"""Return the serializer class for request."""
if self.action == 'list':
return serializers.CompanySerializer
return self.serializer_class
def perform_create(self, serializer):
"""Create a new Company."""
try:
serializer.save()
except IntegrityError:
print('Symbol exists already.')
pass
serializers.py
class CompanySerializer(serializers.ModelSerializer):
"""Serializer for Company."""
class Meta:
model = Company
fields = [
'id', 'name_company', 'symbol', 'cik', 'sector',
'industry_category', 'company_url',
]
read_only_fields = ['id']
def create(self, validated_data):
try:
instance, created = Company.objects.get_or_create(**validated_data)
if created:
return instance
except IntegrityError:
pass
class CompanyDetailSerializer(CompanySerializer):
"""Serializer for Company details."""
class Meta(CompanySerializer.Meta):
fields = CompanySerializer.Meta.fields + ['description']
And right now i am doing unit tests using in this file.
test_company.py
def create_company(**params):
"""Create and return a sample company."""
defaults = {
'name_company': 'Apple',
'symbol': 'AAPL',
'cik': '0000320193',
'sector': 'Technology',
'industry_category': 'Consumer Electronics',
'company_url': 'https://www.apple.com/',
'description':'',
}
defaults.update(params)
company = Company.objects.create(**defaults)
return company
def test_retrieve_companies(self):
"""Test retrieving a list of Companies."""
create_company()
create_company()
create_company(
name_company='Tesla',
symbol='TSLA',
)
res = self.client.get(COMPANIES_URL)
companies = Company.objects.all().order_by('id')
serializer = CompanySerializer(companies, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
I am getting IntegrityError, what i want is that the run time continue without stopping execution raising the error that is why i am testing it inserting APPLE twice.
I am trying to catch the error with this code in the views.py but does not catch it.
def perform_create(self, serializer):
"""Create a new Company."""
try:
serializer.save()
except IntegrityError:
print('Symbol exists already.')
pass
my error is this:
django.db.utils.IntegrityError: duplicate key value violates unique constraint "core_company_symbol_50a489f1_uniq"
DETAIL: Key (symbol)=(AAPL) already exists.
Thank you in advance.

how to apply constraints on Django field within the instances associated with the user

I am making a todo app for practice, the functionality I want to achieve is that, when a user create a task,Only the time field should be unique about all of his tasks, I have done (unique=True) In the time field in model but that make it unique all over the database, but I want it to be unique only with the tasks associated with the user.
the view is below:
#login_required(login_url='login')
def home(request):
tasks = Task.objects.filter(name__username=request.user.username)
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.name = request.user
obj.save()
return redirect('home')
else:
print(request.POST)
print(request.user.username)
messages.warning(request, 'Invalid Data!')
return redirect('home')
context = {'tasks' : tasks}
return render(request, 'task/home.html', context)
task model:
class Task(models.Model):
choices = (
('Completed', 'Completed'),
('In Complete', 'In Complete'),
)
name = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
task = models.CharField(max_length=200, null=False, blank=False)
time = models.TimeField(auto_now_add=False, blank=True)
status = models.CharField(max_length=200, choices=choices, null=True, blank=False)
def __str__(self):
return self.task
def get_task_author_profile(self):
return reverse('profile')
as you can see, I want to show the task that the logged in user has added.
the form is:
class TaskForm(ModelForm):
class Meta:
model = Task
fields = '__all__'
exclude = ['name']
the functionality I talked about above, I tried to achieve through view:
#login_required(login_url='login')
def home(request):
tasks = Task.objects.filter(name__username=request.user.username)
time = []
for task in tasks:
time.append(task['time'])
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid() and form.cleaned_data['time'] != time:
obj = form.save(commit=False)
obj.name = request.user
obj.save()
return redirect('home')
else:
print(request.POST)
print(request.user.username)
messages.warning(request, 'Invalid Data!')
return redirect('home')
context = {'tasks' : tasks}
return render(request, 'task/home.html', context)
but that gave an error: TypeError: 'Task' object is not subscriptable
I know its not right, but how can I achieve it, does Django have anything that can provide such fuctionality?
The problem is coming from here:
for task in tasks:
time.append(task['time']) #<--
Here if you want to use access time, you need to use task.time because task is an object.
Also need to fix another thing in your exisiting code to make it work, because time is a list:
if form.is_valid() and form.cleaned_data['time'] in time:
# ^^^
BTW, you don't need to make it that complicated, you can add Database level constraint from the model to make the times unique for a specific user. Also, use DateTime field for that. You can use unique_togather for that:
class Task(models.Model):
choices = (
('Completed', 'Completed'),
('In Complete', 'In Complete'),
)
name = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
task = models.CharField(max_length=200, null=False, blank=False)
time = models.DateTimeField(auto_now_add=False, blank=True)
status = models.CharField(max_length=200, choices=choices, null=True, blank=False)
class Meta:
unique_togather = ['name', 'time']

Django join button to goal

I try to create changing button after user join to Goal.
I have work buttons, but i don't know how to change this in template.
I try to do this with boolean field, when user click "Join", boolean field change to True, and then in template I try:
{% if goal.joined %}
<Delete join>
{% else %}
<join to goal>
{% endif %}
Models:
class Goal(models.Model, Activity):
title = models.CharField(max_length=255, verbose_name='Tytuł')
image = models.ImageField(upload_to='goals', verbose_name='Tło')
body = HTMLField(verbose_name='Treść')
tags = TaggableManager()
created_at = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = AutoSlugField(populate_from='title')
def __str__(self):
return self.title
class Meta:
verbose_name = 'Cele'
ordering = ['-created_at']
def get_absolute_url(self):
return reverse('goaldetail', args=[str(self.slug)])
#property
def activity_actor_attr(self):
return self.author
def add_user_to_list_of_attendees(self, user):
registration = Joined.objects.create(user = user,
goal = self,
created_at = timezone.now())
def remove_user_from_list_of_attendees(self, user):
registration = Joined.objects.get(user = user, goal = self)
registration.delete()
class Joined(models.Model, Activity):
goal = models.ForeignKey(Goal, on_delete=models.CASCADE, related_name='joined')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='joined_users')
created_at = models.DateTimeField(auto_now_add=True)
joined = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self.id is None and self.created_at is None:
self.created_at = datetime.datetime.now()
self.joined = True
super(Joined, self).save(*args, **kwargs)
#property
def activity_actor_attr(self):
return self.user
Views:
def joined_add(request, pk):
this_goal = Goal.objects.get(pk=pk)
this_goal.add_user_to_list_of_attendees(user=request.user)
return redirect('goaldetail', slug=this_goal.slug)
def joined_delete(request, pk):
this_goal = Goal.objects.get(pk=pk)
this_goal.remove_user_from_list_of_attendees(request.user)
return redirect('goaldetail', slug=this_goal.slug)
def goaldetail(request, slug):
goal = get_object_or_404(Goal, slug=slug)
return render(request, 'goals/detail.html',
{'goal': goal})
Path:
path('joined/<int:pk>', views.joined_add, name='joined_add'),
path('joined-delete/<int:pk>', views.joined_delete, name='joined_delete'),
Create and delete works perfect, but on template this button don't change. I try in many ways and don't know how to do this.

Find where in Django code an 'incorrect type error' is being thrown for a POST

I am trying to discover where in my Django code an incorrect type error message is being thrown:
{"_body":"{\"keywords\":[\"Incorrect type. Expected pk value, received str.\"]}","status":400,"statusText":"Ok","headers":{"Content-Type":["application/json;q=0.8"]},"type":2,"url":"http://127.0.0.1:8000/api/items/"}
I have put a breakpoint in the view's perform_create that handles the POST but this is never executed.
Anyone know where in my code I should set a breakpoint to take a look at what is happening?
My view looks like this:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all().order_by('-date_added')
serializer_class = ItemSerializer
def list(self, request, *args, **kwargs):
this_user = self.request.query_params.get('user', None)
count_of_views = Seen.objects.filter(who_saw=this_user).count()
custom_data = {
'results': ItemSerializer(self.get_queryset(), many=True).data
}
custom_data.update({
'views': count_of_views
})
return Response(custom_data)
def perform_create(self, serializer):
creator = User.objects.get(pk=self.request.data['owner_id'])
the_keywords = self.request.data['keywords'].split
serializer.save(owner=creator)
#serializer.save(keywords=the_keywords)
EDIT: And the serializer is:
class ItemSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
def get_username(self, obj):
value = str(obj.owner)
return value
def get_keywords(self, obj):
value = str(obj.keywords)
return value
class Meta:
model = Item
fields = ('id', 'url', 'item_type', 'title', 'keywords')
EDIT Item model is:
class Item(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=60, default='')
url = models.CharField(max_length=250, default='', unique=True)
keywords = models.ManyToManyField(Keyword, related_name='keywords')
Keyword model is:
class Keyword(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return format(self.name)

saving django ManyToMany not valid

I have a form from my model that needs to be validated and saved making use of ManyToMany Fields.
Everytime I try and save it, I get thrown back to the page, just saying this field is required
My models.py
class HuntingReport(models.Model):
user = models.ForeignKey(User, related_name='User')
outfitter = models.ForeignKey(User, related_name='Outfitter', null=True, blank=True)
date_travel_started = models.DateField(blank=True, null=True)
date_travel_ended = models.DateField(null=True, blank=True)
title = models.CharField(max_length=50)
report = models.TextField()
wish_list = models.ManyToManyField(Specie)
bag_list = models.ManyToManyField(Trophies)
def __unicode__(self):
return self.title
My forms.py looks as follows
class HuntingReportForm(ModelForm):
date_travel_started = forms.DateField(widget=extras.SelectDateWidget(years=range(1970,2010)))
date_travel_ended = forms.DateField(widget=extras.SelectDateWidget(years=range(1970,2010)))
wish_list = forms.ModelMultipleChoiceField(queryset=Specie.objects.all(), widget=FilteredSelectMultiple("verbose name", is_stacked=False))
bag_list = forms.ModelMultipleChoiceField(queryset=Trophies.objects.all(), widget=FilteredSelectMultiple("verbose name", is_stacked=False))
class Meta:
model = HuntingReport
exclude = ['user']
def __init__(self, user, *args, **kwargs):
super(HuntingReportForm, self).__init__(*args, **kwargs)
users = User.objects.filter(userprofile__outfitter=True)
self.fields['outfitter'].choices = [('', '')] + [(user.pk, user.get_full_name()) for user in users]
my views.py
def create(request, template_name='reports/new.html'):
if request.method == 'POST':
form = HuntingReportForm(request.POST, request.FILES)
if form.is_valid():
newform = form.save(commit=False)
newform.user = request.user
newform.save_m2m()
return HttpResponseRedirect('/hunting-reports/')
else:
form = HuntingReportForm(request.user)
context = { 'form':form, }
return render_to_response(template_name, context,
context_instance=RequestContext(request))
Did you try passing blank=True for model field's constructor, or required=False for the ModelMultipleChoiceField's constructor?
I know that blank=True solves the problem for the form in the admin panel, but I don't know how it gets mapped to the ModelForm's fields. I'm assuming that it gets mapped to required property.