Django global variable based on user groups - django

In my Django project I have a database that is populated from outside Django, but needs the Django functionality to display data in the database in a user friendly manner. The legacy database structure is as follows:
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
The users have a custom User profile with manytomany relationship with group names assigned to the specific user:
class CustomUser(models.Model):
#some values
projects = models.ManyToManyField(AccessGroup)
Currently I am able to display data from all groups a user has access to, but what I am looking for is a way to create a drop down menu so that users can switch between groups without the need to log out or reenter group on every view.

You could try something like this:
AccessGroup.objects.filter(CustomUser__pk=1)
Or
CustomUser.objects.filter(AccessGroup__group_name='GropName')
https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_many/

you can extend the django user model, somthing like
from django.contrib.auth.models import User
class CustomUser(models.Model):
projects = models.ManyToManyField(AccessGroup)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
custom_user = models.ForeignKey(CustomUser, unique=False)
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
then something like this to get the data in your view
def index(request):
AccessGroup.objects.filter(user__id=persion(request.user).id)

I'll assume you know how to get the list of groups, and are just looking as to how to get this list into templates. If not, let me know and I'll explain that as well.
If you're trying to get a global variable into templates, there are really 3 main options:
Make a custom template tag that takes the current user as input, and generates this list as output.
Use Middleware to generate the list, and append it to the current context for each request
Use a method on your user class, or a mixin of it (really easy if you use a custom user class), and just call that method as user.method in your templates. Remember to exclude parentheses from the method call (only in templates), and keep in mind that this method shouldn't accept any parameters other than self.

Thank you everybody for getting me on the right track. What I ended up doing is writing a context processor for checking the user permissions:
#context_processors.py
def check_groups(request):
group_check = AccessGroup.objects.values('id','group_name').filter(projects=request.user.id)
return {
'group_check': group_check,
}
Afterwards I created a Bootstrap-select dropdown in my base.html
<select class="selecpicker">
<optgroup>
<option data-hidden="true">Choose group</option>
{% for grpup in group_check %}
<option val="group.id">{{ group.group_name }}</option>
{% endfor %}
</optgroup>
And the it is just a matter of users using it as means to switch access groups in views and passing the value via ajax to any other template views I come across.
Not the 100% what I was looking for, but it works and my users are happy.

Related

Create object if doesn't exist when calling it by related_name

NOTE: I'm aware of get_or_create and it does not apply here.
I have a Django app with a lot of users. One of my project apps must keep some kind of user preferences relating to this app, so in its' models.py I've created:
[models.py]
class UserPreferences(models.Model):
user = models.ForeignKey(
get_user_model(),
related_name='adminpanel_preferences'
)
advanced_options_enabled = models.BooleanField(default=False)
force_dark_mode = models.BooleanField(default=False)
... (more boolean fields, integer choices etc)
I don't want to use signals for creating a UserPreferences objects every time new user is being created. My idea was to only create an object when such object is somehow requested. This might sound like a perfect get_or_create use case, however I need to call this object from a template using the related_name e.g:
[template.html]
{% if request.user.adminpanel_preferences.hardcore_mode_enabled %}
...
{% endif %}
And while normally the request.user.adminpanel_preferences would return UserPreferences.None, I want it to silently create the object (using default field values) instead.
Is there a possibility to achieve such behavior without interfering the User model? The UserPreferences are not global app preferences, they only refer to this particular app and I'd love to keep these two independent.
I have also tried using a custom objects manager with customized get_queryset method but it doesn't seem to work when using the related_name.

Adding a new ManyToMany relationship in a form?

Django newbie here. I keep encountering the exact same design paradigm, which seems like it should be common for everyone, yet can't find out how it's supposed to be resolved.
Picture the following ManyToMany relationships:
An organization could have many members; each person could be a member of many organizations
An organization could manage many objects. An object could be in use by multiple organizations. The same applies to the relationship between people and objects.
An organization, person, or object could have multiple media elements (photos, videos, etc) of it, and a single media element could be tagged with numerous organizations, people, or objects
Nothing unusual. But how does a site user add a new person, organization, or object? It seems that if someone is filling out an "add an organization" form, in addition to choosing from existing people, objects, media, etc there should be a button for "new member", "new object", "new photo", etc, and this should take you to the forms for creating new members, objects, media, etc. And when they're done, it should go back to the previous page - whose form filled-out form entries should persist, and the newly created entry should be listed in its respective ManyToMany field.
The problem is, I don't know how to do this. I don't know how one would add a button in the middle of a form, and can't seem to find anything to clarify how to do it. I assume it would need to be a submit button, with a different name / id or some other way so that views.py can treat it differently, via flagging an "incomplete" record in the database. And the new form will need to be passed information about what page it needs to go back to when it's submitted.
Am I thinking about this correctly? If so, then I think the only knowledge I lack is how to add a second submit button in a form and how to recognize its usage in views.py.
If I'm not thinking about this correctly, however, please suggest an alternative paradigm that you think makes more sense :) This is my first Django project, so I'm learning as I do it.
ED: I'm thinking maybe instead of using {{ form.as_p }} to display it, I need to iterate over fields and use some logic to add the extra submit button in the middle as html: What's the best way to add custom HTML in the middle of a form with many fields?
Then I'll just need to figure out a way to detect which submit button was used and put some logic behind it to handle partially-submitted forms, redirecting to a form to create the relation, and then redirecting back on submit... I can probably figure this out...
The first thing I would recommend is to define your models. Lay them all out with the attributes you require. That'll be the foundation for everything else you want to accomplish. You can do everything you mentioned with Django... it's just a matter of coding it. As far as I know you would need to create each model instance separately, and then you can refer to already created instances in the create form for the Organization model for example. I would look into the docs for generic views that help you create objects easily. Then you can link to other create forms if you wish. I don't know how you can create multiple instances of different models in one form, and I don't think it would be the best way to do things even if you can. Here's an example of a model, a create form, a create view, and corresponding url:
# models.py
class Organization(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
# forms.py
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = ('name',)
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
def clean(self):
cleaned_data = super(OrganizationForm, self).clean()
name = cleaned_data.get('name')
# views.py
class OrganizationCreateView(CreateView): # inherits from CreateView
form_class = OrganizationForm
template_name = 'create_org.html'
success_url = 'success'
def form_valid(self, form): # validate the form and save the model instance
org = form.save(commit=False)
org.save()
return redirect(reverse('redirect_url'))
# urls.py
from Project.apps.app_name import views as app_views
app_name = 'app_name'
urlpatterns = [
url(r'^create_org/$', app_views.OrganizationCreateView.as_view(), name='create_org'), # as_view() is used for class based views
# create_org.html
<form method="post">
{% crsf_token %}
{{ form.as_p }}
<a href="{% url 'app_name:create_person' %}>Create person</a> # You can link to other create views, and just style the link as a button.
<input type="submit" value="Submit">
</form>
Hope that helps.

Protecting user's model view from other users

I'm trying to write a generic DetailView for my model (which is related to User), but I don't know how to limit access for specific model (view) for only that User (owner). I've looked into docs but haven't found any guide how to do that the proper way. So far I've managed to overwrite test_func() method of the class using UserPassesTestMixin where i check if object.user == self.request.user but I'm not sure if that's the proper way to do that.
What I've just said may not be clear, so e.g.
Model A of id 4 is related (owned by) User A.
Now, if User B tries to request Model A DetailView (/models/4) he should be rejected with 403 Error Code, or even better, custom error page.
Have you tried a query like this?
MyTable.objects.filter(user=self.request.user)
This only returns the objects related to the current user.
There are few ways you can try
verify user permissions in your own views using the has_perm method provided in the user model.
if user.has_perm('foo.add_bar'):
return HttpResponse("You are authorized to add content!")
else:
return HttpResponse("Permission to add denied")
verify in your templates using the perms variable that is automatically added to the template context.
{% if perms.app_label.can_do_something %}
This content will be shown users with can_do_something permission.
{% endif %}
This content will be shown to all users.
You can also create your own permissions to the models
class SomeModel(models.Model):
owner = models.ForeignKey(User)
content = models.TextField()
class Meta:
permissions = (
('view_content', 'View content'),
)
have a look at this link it will give you an idea.

How to display multiple forms of a single model in Django templates?

I have this model Note:
class Note(models.Model):
category = models.ForeignKey(Category)
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=40)
text = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
And I want to display this form:
class NoteEditForm(forms.ModelForm):
class Meta:
model = Note
fields = ('title', 'text')
in a template, but I want it to appear for each existing Note object in the database (it has to be that way). I've done something like that but then I hardcoded the form, pointing to the edit view URL with each object pk as a parameter; but I'm sure it has to be a clearer way, just I haven't found it. Could you guys help me with that? Thanks!
The easiest way to do this is to use a formset (also see model formsets).
For your Note model and NoteEditForm you could do something like this. You'd usually put this wherever you've defined your NoteEditForm but it can go in another file, such as views.py.
from django.forms import modelformset_factory
NoteEditFormSet = modelformset_factory(Note, form=NoteEditForm)
Using NoteEditFormSet in a view and template is almost the same as using a regular form, but there are a few differences to be aware of if you want to do anything complicated so have a look at the docs (view and template). If that's not clear enough, add a few details of what you're trying to do in your view and template and I'll try to help.
By default the formset will use Note.objects.all() as its queryset, which is what you say you want, but you can change that (details are covered in the docs).
Update:
To save an individual Note with an AJAX request I would add a second view to handle those requests. So if your formset for all Notes is served by a view at /notes/, you could add a view to handle your AJAX request at /notes/<note_id>/ (obviously just an example, adjust to fit your URL structure).
Then your JS on the /notes/ page is responsible for serializing the data for a single note and making the request to /notes/<note_id>/ (remember the CSRF token).
The HTML inputs generated for the formset have their IDs prefixed with something like id_form-<number>- and there are hidden inputs containing Note primary keys which will let you work out which ID prefix applies to each note.
I would think about doing it like this
{% for note in Notequeryset %}
<form action={% url 'url_name_to_form' pk={{note.pk}} %}>
{{form.as_p}}
</form>
{% endfor %}
Let me know what you think

Check if model instance is FKed inside a queryset?

I have a model:
class ProjectBookmark(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey('Project')
say a user is viewing a project, is there a way to somehow check if current project is in users bookmarks other than a nasty way I do this now via request context as bellow? Basically, users bookmarks are a list of projects and checked as
{% if current_project in current_user.bookmarks %}...
right from inside the template?
the_user = request.user
bookmarked_projects = list()
for b in ProjectBookmark.objects.filter(user = request.user):
bookmarked_projects.append(b.project_id)
the_user.bookmarks = Project.objects.filter(id__in=bookmarked_projects)
return {'current_user':request.user}
For use directly in a template, the best way to go would be creating a custom template tag. Check out the template tag docs.
As for the filtering code itself, it would be very helpful to provide related_name arguments to the ForeignKey fields in the model definition. For example:
class ProjectBookmark(models.Model):
user = models.ForeignKey(User, related_name="project_bookmarks")
project = models.ForeignKey('Project', related_name="bookmarks")
This way you could access a QuerySet object containing all the users bookmarks using request.user.project_bookmarks.all()