Django viewflow custom permissions - python

Probably something simple. I am trying to follow the cookbook example on the following link https://github.com/viewflow/cookbook/tree/master/guardian. With the exception of a couple of unrelated differences between the example and my own code (I am not using frontend and am using custom views). Everything else works as expected. I do not understand what I am getting wrong on the permissions side of things.
I am getting a "403 forbidden" error whenever a user other than the one that started the process tries to interact with the flow. This happens irrespective of the assigned user's assigned permissions - is this the expected behavior or should I open a ticket on Github?
While I am trying to understand if viewflow can support what I am trying to accomplish - I would like to leave apply the permissions checking on my own views (rather than the built in checks). I saw that there was a pull request https://github.com/viewflow/viewflow/issues/252 - however, I do not understand how to implement it.
Any help would be appreciated! Been stuck on this for quite a while
The permissions are defined in a custom user class
accounts/models.py
class Department(models.Model):
name = models.CharField(unique=True, max_length=250)
description = models.TextField(blank=True)
objects = managers.DepartmentManager()
class Meta:
permissions = [
('can_accept_bill', 'Can accept department bill'),
('can_validate_bill', 'Can validate department bill'),
('can_set_bill_paydate', 'Can set payment date for department bill'),
('can_pay_bill', 'Can pay department bill'),
flows.py
class OrderFlow(Flow):
process_class = models.OrderProccess
task_class = models.OrderTask
lock_impl = select_for_update_lock
start = (
flow.Start(
views.StartView)
.Permission(auto_create=True)
.Next(this.approve_budget)
)
approve_budget = (
flow.View(
views.BudgetApprovalView)
# .Permission(auto_create=True)
.Permission(
'order.can_accept_bill',
obj= lambda act: act.process.order.department
)
.Assign(lambda act: act.process.created_by)
.Next(this.check_budget_approval)
)
check_budget_approval = (
flow.If(
cond=lambda act: act.process.order.budgetholder_approved
)
.Then(this.approve_finance)
.Else(this.approve_budget)
)
approve_finance = (
flow.View(
views.FinanceApprovalView)
.Permission(auto_create=True)
.Assign(lambda act: act.process.created_by)
.Next(this.check_finance_approval)
)
models.py
class Order(models.Model):
department = models.ForeignKey(account_models.Department, on_delete=models.CASCADE)
description = models.CharField(max_length=30)
project = models.ForeignKey(project_models.Project, on_delete=models.CASCADE)
# other unrelated code
class OrderProccess(Process):
order = models.ForeignKey(Order, blank=True, null=True, on_delete=models.CASCADE)
class OrderTask(Task):
class Meta:
proxy = True
views.py
class StartView(StartFlowMixin, generic.UpdateView):
model = models.Order
form_class = forms.OrderForm
def get_object(self):
return self.activation.process.order
# other form handling code
class OrderView(FlowMixin, generic.UpdateView):
def get_object(self):
return self.activation.process.order
class BudgetApprovalView(FlowMixin, generic.UpdateView):
form_class = forms.BudgetHolderApproval
def get_object(self):

Related

Django: Block access to not authorized users in CreateView

I have a problem for block access to not authorized user in pages dedicated to add new objects. List of that users is stored in many-to-many field in project object and in foreign key field.
Below is models.py
class Project(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects_as_owner", null=True)
project_managers = models.ManyToManyField(User, related_name="projects_as_pm", blank=True)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
date_of_insert = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Milestone(models.Model):
project_fk = models.ForeignKey(Project, related_name="milestones", on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
date_of_insert = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
And views.py with class I have problem
class NewMilestone(LoginRequiredMixin, generic.CreateView):
model = Milestone
fields = ['name', 'description']
lookup_url_kwarg = 'p_id'
template_name = 'main/new_milestone.html'
# ... two functions, that work good, not important here ...
def get_queryset(self):
qs = super(NewMilestone, self).get_queryset()
project = Project.objects.get(id=self.kwargs['p_id'])
if(qs.filter(project_fk__owner=self.request.user).exists() or User.objects.filter(id=self.request.user.id).filter(projects_as_pm__id=project.id).exists()):
return qs
else:
return Http404("You are not authenticated to this action")
Objective here is here to allow authenticated users (owner and project manager/s) to enter this view and for anybody else show info about declined access.
Problem is that, that method, get_queryset, doesn't block unauthorised users in CreateViev class.
I tried some configurations for that issue, every single one I used had this flaw.
My question here is how to make it work the way I expect from it?
PS. English is not my native language and it was a while since I wrote something, so please be understanding.
You are using the LoginRequiredMixin which is a good thing. But then you didn't set any of the parameters available.
LoginRequiredMixin inherits from AccessMixin and you can use all it's parameters with which it shouldn't be too complicated to cover your case.
Here's a possible implementation:
class NewMilestone(LoginRequiredMixin, generic.CreateView):
...
# your class attributes
...
raise_exception = True
# Returns a permission denied message. Default: empty string
def get_permission_denied_message(self):
return "Access is restricted to authenticated users"
If you have raise_exception set to True then the get_permission_denied_message method will be called. Otherwise the user will be redirected to the login_url which you also would have to declare as a class attribute.

Find entities for specific user through multiple tables in Django Admin

I searched through stackoverflow about this particular scenario, but could not find a concrete answer, so i'm posting this.
So my problem is that i need to display specific records to a specific user in Django Admin. I'm aware that i can get the concrete logged in user through the get_queryset method extracting it from the request object. But the issue is i need to look through 6 tables to get to the information about the user of the recommendations so i could know which recommendation to display to him.
For example, if the records i need to display come from a Recommendation table, it has a reference to TableA, which has a reference to TableB .... which has a reference to TableF which has a reference to the User.
I'm aware i could do this by executing a plain SQL query with multiple joins, but my guess is that there must be a pythonic or Django sophisticated solution to this. But i may be wrong.
The model is unfortunately not in my control, nor i can change it, so i'm left to work with the state of the model that there is.
Thanks in advance.
EDIT: Unfortunately, i can't share details of it, but i can share the general look of it. So i think this should be enough to have a picture of my problem.
from django.db import models
from django.contrib.auth.models import User
class TableF(models.Model):
information = models.CharField(max_length=256, null=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class TableE(models.Model):
information = models.CharField(max_length=256, null=False)
tableF = models.ForeignKey(TableF, on_delete=models.CASCADE)
class TableC(models.Model):
information = models.CharField(max_length=256, null=False)
tableEs = models.ManyToManyField(TableE, through='TableD')
class TableD(models.Model):
information = models.CharField(max_length=256, null=False)
tableC = models.ForeignKey(TableC, on_delete=models.CASCADE)
tableE = models.ForeignKey(TableE, on_delete=models.CASCADE)
class TableA(models.Model):
information = models.CharField(max_length=256, null=False)
tableCs = models.ManyToManyField(TableC, through='TableB')
class TableB(models.Model):
information = models.CharField(max_length=256, null=False)
tableA = models.ForeignKey(TableA, on_delete=models.CASCADE)
tableC = models.ForeignKey(TableC, on_delete=models.CASCADE)
class Recommendation(models.Model):
information = models.CharField(max_length=256, null=False)
tableA = models.ForeignKey(TableA, on_delete=models.CASCADE)
you can use a middleware to include de user to the thread locals and catch this user from get_queryset in the model manager.
from threading import local
_thread_locals = local()
def get_current_user():
return getattr(_thread_locals, 'user', None)
class ThreadLocals(object):
#staticmethod
def process_request(request):
_thread_locals.user = getattr(request, 'user', None)
in the settings
MIDDLEWARE = [
...
'path.to.file.ThreadLocals',
]
from your.path import get_current_user
class TableFManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(creator=get_current_user())
class TableF(models.Model):
information = models.CharField(max_length=256, null=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
objects = TableFManager()
another less invasive option could be to rewrite the get_queryset in the admin class. there you already have the user in the request
def get_rec_user(user):
tes = TableE.objects.filter(tableF__in=TableF.objects.filter(user=user))
aes = TableB.objects.filter(tableE__in=tes).values_list('tableA_id', flat=True)
return Recommendation.objects.filter(
tableA__in=TableA.objects.filter(id__in=aes)
)

Django - Membership matching query does not exist

I'm trying to create a subscription style service on Django (using Django3)
Basically, I had it working..ish, but my stripe customer keys weren't being added in, so I fiddled around to get that sorted.
Now I'm getting this error - Membership matching query does not exist. this is whenever I'm trying to create an account, login or login to the admin of my Django project.
I can't really figure out what the issue is, as I've reverted the changes I made to get the cust-Id (any tips here would be appreciated! lol) working again, but it's still throwing it up at me.
Here's my code in my models, I can't even pinpoint where this issue is coming from if I'm honest. (I got this mostly from the JustDjango tutorial btw!)
The debug thing is saying it's to do with the free_membership variable in the post_save_usermembership_create method though.
So far I just have models.py and admin.py done to get this up & running -
from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
from datetime import datetime
import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
MEMBERSHIP_CHOICES = (
('Regular', 'reg'),
('Premium', 'premium'),
('Free', 'free')
)
class Membership(models.Model):
slug = models.SlugField()
membership_type = models.CharField(
choices=MEMBERSHIP_CHOICES,
default='Free',
max_length=30)
price = models.IntegerField(default=15)
stripe_plan_id = models.CharField(max_length=40)
def __str__(self):
return self.membership_type
class UserMembership(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
stripe_customer_id = models.CharField(max_length=40)
membership = models.ForeignKey(
Membership, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.user.username
def post_save_usermembership_create(sender, instance, created, *args, **kwargs):
user_membership, created = UserMembership.objects.get_or_create(
user=instance)
if user_membership.stripe_customer_id is None or user_membership.stripe_customer_id == '':
new_customer_id = stripe.Customer.create(email=instance.email)
free_membership = Membership.objects.get(membership_type='Free')
user_membership.stripe_customer_id = new_customer_id['id']
user_membership.membership = free_membership
user_membership.save()
post_save.connect(post_save_usermembership_create,
sender=settings.AUTH_USER_MODEL)
class Subscription(models.Model):
user_membership = models.ForeignKey(
UserMembership, on_delete=models.CASCADE)
stripe_subscription_id = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.user_membership.user.username

How to authenticate user for a specific object rather than whole class in django?

I am working on making an app to add clubs in website. This is my model.py file
from django.db import models
from stdimage import StdImageField
# Create your models here.
class Club(models.Model):
ClubName = models.CharField(max_length=200)
ClubLogo = StdImageField(upload_to='club_logo', variations={'thumbnail':(150, 200, True)})
ClubDetails = models.TextField()
ClubStartDate = models.DateField()
def __str__(self):
return self.ClubName
class Notice(models.Model):
NOTICE = 'NOTICE'
UPDATES = 'UPDATES'
EVENTS = 'EVENTS'
NOTICE_IN_CHOICES = (
(NOTICE, 'Notice'),
(UPDATES, 'Updates'),
(EVENTS, 'Events'),)
NoticeType = models.CharField(
max_length=20, choices=NOTICE_IN_CHOICES, default=NOTICE)
NoticeTag = models.CharField(max_length=30)
NoticeStartDate = models.DateField(auto_now_add=True)
NoticeEndDate = models.DateField()
NoticeFile = models.FileField(default='#', upload_to='notice/%Y/%m/%d')
NoticeContent = models.TextField(default='NA')
NoticeClub = models.ForeignKey(Club)
def __str__(self):
return self.NoticeTag
class Members(models.Model):
MemeberName = models.CharField(max_length=200)
MemberImage = StdImageField(upload_to='member_photo', variations={'thumbnail':(150, 120, True)})
MemberEmail = models.EmailField()
MemberClub = models.ForeignKey(Club)
def __str__(self):
return self.MemeberName
Now when i am making users via django's inbuilt admin panel i have option to give permission to users to change member of any club but i want to give access to change members of only that particular club which he is member of.
As you can see in this picture that all club are in dropdown option when someone who has access to add notices adding otices. But instead of that i want only one option in the dropdown for the useradmin to which he is associated.
this is my admin.py file
from django.contrib import admin
# Register your models here.
from club.models import Club, Members, Notice
admin.site.register(Club),
admin.site.register(Members),
admin.site.register(Notice),
This is a problem with which many users have been struggling with.
I have been using couple of external packages, and couple of self made solutions. But the best one I have found so far is Django Guardian It's an implementation of per object permission .This means you can manage users and permissions to which they have access to.

Django drf-nested-routers - model object has no attributed related field

I am creating an API using the drf-nested-routers application for Django Rest Framework. This application is a tracker where users have sessions and tasks. Each user can have three active tasks and can work on each of these tasks in a given session.
My (abbreviated) models are:
#models.py
class User(models.Model):
name = models.Charfield()
class Task(models.Model):
start_date = models.Datefield()
task_title = models.Charfield()
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Session(models.Model):
session_date = models.Datefield()
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sessions')
task_one = models.ForeignKey(related_name="task_one")
task_one_attempts = models.IntegerField()
task_two = models.ForeignKey(related_name="task_two")
task_two_attempts = models.IntegerField()
I have created the following (abbreviated) Serializers for these models:
#serializers.py
class TaskSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=False)
class Meta:
model = Task
fields = ('start_date', 'task_title', 'user')
class SessionSerializer(serializers.ModelSerializer):
user = Serializers.StringRelatedField(many=False)
class Meta:
model = Session
fields = ('session_date', 'user', 'task_one', 'task_one_attempts', 'task_two', 'task_two_attempts')
class UserSerializer(models.ModelSerializer):
sessions = SessionSerializer(many=True)
tasks = TaskSerializer(many=True)
sessions = SessionSerializer(many=True)
class Meta:
model = Users
fields = ('name', 'sessions', 'tasks')
I also have my views.py and urls.py set up to do the routing properly.
I can navigate to the sessions and tasks API views just fine. However, whenever I try to navigate to the user view, it throws the following error:
'User' object has no attribute 'tasks'.
What's really interesting, though, is that if I remove 'tasks' and just include sessions, it serializes everything just fine and gives me a nested view of the User's various sessions.
I'm at a loss here and would appreciate any assistance.
I rubber-ducked it with my wife and figured out my problem.
I had 'related_name="sessions"' in my ForeignKey field for user in models.py.
I was missing that information in the ForeignKey field in the task model.
Hopefully someone else stumbles on this and can learn from my mistake.

Categories