I want to add many users to group based on filter and my selection in admin panel.
But the user change list view does not provide such action.
So is there a way to do this? Any snippet or app?
No shell please because I hope people other than developers can do it.
I am surprised Django does not provide a way for such a common task.
Thanks!
This can save some time but not flexible. I can't filter and really "bulk" add.
I'm afraid this particular functionality is not yet exposed on the panel. You'll have to roll your sleeves and open up the shell. You can use the following snippet, just put your filters where .get() and .exclude() are defined.
$ python manage.py shell
>>> from django.contrib.auth.models import User, Group
>>> my_group = Group.objects.get(name='groupname')
>>> my_users = User.objects.filter(<your_filter>)
>>> for my_user in my_users:
... my_group.user_set.add(my_user)
Create A new Form Like This to add users to group in bulk
from django.forms import ModelForm
from django import forms
from django.contrib.auth.models import Group, User
class GroupAdminForm(ModelForm):
class Meta:
model = Group
group_users = forms.ModelMultipleChoiceField(label=u'Usuários deste Grupo', queryset=User.objects.all())
def __init__(self, *args, **kwargs):
super(GroupAdminForm, self).__init__(*args, **kwargs)
users_id = list()
# Avoid 'No exception supplied'
try:
users = self.instance.group.user_set.all()
for u in users:
users_id.append(u.id)
if self.fields.has_key('group_users'):
self.fields['group_users'].initial = users_id
except Exception, e:
pass
def clean(self):
group = self.cleaned_data['group']
group.save()
if group.user_set.all():
group.user_set.clear()
try:
users = self.cleaned_data['group_users']
for user in users:
group.user_set.add(user)
except:
return self.cleaned_data
return self.cleaned_data
Related
I am new to django and I am a bit confused on how the permission works, or if that is what I am supposed to use in my case.
So, I have my user/model:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
ROLE_CHOICES = (
(0, ('Student')),
(1, ('Proffesor')),
(2, ('Administration'))
)
role = models.IntegerField(choices=ROLE_CHOICES, default=2)
And then I have my views in election/views.py:
class MainPage(View)
class ElectionList(LoginRequiredMixin, View)
class ElectionDetail(LoginRequiredMixin, View)
#only administration can create elections
class CreateElection(LoginRequiredMixin, CreateView)
How can I restrict a simple user (student, for example) to create an election?
Django already has a Permission and Group models and per-group permissions, so the cleanest "django compatible" way here would be to define "Students", "Professors" and "Administrators" as groups, setup their permissions (eventually adding custom ones if needed), add your users to the appropriate group(s), and test if your current user has the needed permissions for a given action using the permission_required decorator or since you're using a class-based view the PermissionRequiredMixin.
As a side note: since you're using ints for your role values, you may want to add pseudo_constants for them in your model:
class User(AbstractUser):
ROLE_STUDENT = 0
ROLE_PROFESSOR = 1
ROLE_ADMINISTRATOR = 2
ROLE_CHOICES = (
(ROLE_STUDENT, 'Student'),
(ROLE_PROFESSOR, 'Professor'),
(ROLE_ADMINISTRATOR, 'Administration')
)
So you can query / filter your model using sensible human-readable values instead of magic numbers, ie:
students = User.objects.filter(role=User.ROLE_STUDENT)
instead of
students = User.objects.filter(role=0)
But if you use contrib.auth.models.Group for permissions you may not even need this field at all, as you can get your queryset from the groups members.
You can use UserPassesTestMixin
eg.,
class LoginAndPermission(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
return self.request.user.is_student
def get_login_url(self):
if self.request.user.is_authenticated():
# User is logged in but does not have permission
return "/permission-denied-url/"
else:
# User is not logged in
return "/login/"
class ElectionDetail(LoginAndPermission, View):
My solution could be an alternative of Django's Decorator.
I'm pretty interesting by your question.
When I have function in my views and I don't want to display this one to a user group, I have a templatetags file :
from django import template
from django.contrib.auth.models import Group
register = template.Library()
#register.filter(name='has_group')
def has_group(user, group_name):
group = Group.objects.get(name=group_name)
return group in user.groups.all()
Then, in my HTML file :
{% if request.user|has_group:"admin" %}
<li>Some part</li>
{% endif %}
I think it's possible in my templatetags to user permission directly in my views.py file, but I don't know How to do that.
Anyway, my method works very well up to now ;)
Read the Permission and Authorization documentation in https://docs.djangoproject.com/en/1.11/topics/auth/default
from django.contrib.auth.mixins import AccessMixin
class AddElectionPermission(AccessMixin):
raise_exception = True
permission_denied_message = 'permission deny'
def dispatch(self, request, *args, **kwargs):
if request.user.role != 0:
return self.handle_no_permission()
return super(AddElectionPermission, self).dispatch(request, *args, **kwargs)
#only administration can create elections
class CreateElection(LoginRequiredMixin, AddElectionPermission, CreateView)
How to remove the default delete action in Django admin?
Would the following work?
actions = [ ]
This works:
def get_actions(self, request):
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
It's also the recommended way to do this based off Django's documentation below:
Conditionally enabling or disabling actions
In your admin class, define has_delete_permission to return False:
class YourModelAdmin(admin.ModelAdmin):
...
def has_delete_permission(self, request, obj=None):
return False
Then, it will not show delete button, and will not allow you to delete objects in admin interface.
You can disable "delete selected" action site-wide:
from django.contrib.admin import site
site.disable_action('delete_selected')
When you need to include this action, add 'delete_selected' to the action list:
actions = ['delete_selected']
Documentation
If you want to remove all the action:
class UserAdmin(admin.ModelAdmin):
model = User
actions = None
If you want some specific action:
class UserAdmin(admin.ModelAdmin):
model = User
actions = ['name_of_action_you_want_to_keep']
You can globally disable bulk delete action and enable for selected models only.
Documentation from django website
# Globally disable delete selected
admin.site.disable_action('delete_selected')
# This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin):
actions = ['some_other_action']
...
# This one will
class AnotherModelAdmin(admin.ModelAdmin):
actions = ['delete_selected', 'a_third_action']
...
Giving credit to #DawnTCherian, #tschale and #falsetru
I used:
class YourModelAdmin(admin.ModelAdmin):
...
def get_actions(self, request):
actions = super(YourModelAdmin, self).get_actions(request)
try:
del actions['delete_selected']
except KeyError:
pass
return actions
def has_delete_permission(self, request, obj=None):
return False
It removes the delete action from the list view and the delete option from the detail view.
If you are using that model, as a foreign key in some other model.
Then by using PROTECT constraint for that foreign key you can disable deletion for that model in Django admin.
For Example,
class Exam(models.Model):
student = models.ForeignKey(User, on_delete=models.PROTECT)
marks = models.IntegerField(default=0)
By adding PROTECT constraint to User model through the foreign key present in Exam model, I have disabled the power (in Django admin or elsewhere) to delete students (User) who have written exams.
I have 2 models - for example, Book and Page.
Page has a foreign key to Book.
Each page can be marked as "was_read" (boolean), and I want to prevent deleting pages that were read (in the admin).
In the admin - Page is an inline within Book (I don't want Page to be a standalone model in the admin).
My problem - how can I achieve the behavior that a page that was read won't be deleted?
I'm using Django 1.4 and I tried several options:
Override "delete" to throw a ValidationError - the problem is that the admin doesn't "catch" the ValidationError on delete and you get an error page, so this is not a good option.
Override in the PageAdminInline the method - has_delete_permission - the problem here -it's per type so either I allow to delete all pages or I don't.
Are there any other good options without overriding the html code?
Thanks,
Li
The solution is as follows (no HTML code is required):
In admin file, define the following:
from django.forms.models import BaseInlineFormSet
class PageFormSet(BaseInlineFormSet):
def clean(self):
super(PageFormSet, self).clean()
for form in self.forms:
if not hasattr(form, 'cleaned_data'):
continue
data = form.cleaned_data
curr_instance = form.instance
was_read = curr_instance.was_read
if (data.get('DELETE') and was_read):
raise ValidationError('Error')
class PageInline(admin.TabularInline):
model = Page
formset = PageFormSet
You could disable the delete checkbox UI-wise by creating your own custom
formset for the inline model, and set can_delete to False there. For
example:
from django.forms import models
from django.contrib import admin
class MyInline(models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(MyInline, self).__init__(*args, **kwargs)
self.can_delete = False
class InlineOptions(admin.StackedInline):
model = InlineModel
formset = MyInline
class MainOptions(admin.ModelAdmin):
model = MainModel
inlines = [InlineOptions]
Another technique is to disable the DELETE checkbox.
This solution has the benefit of giving visual feedback to the user because she will see a grayed-out checkbox.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
if some_criteria_to_prevent_deletion:
form.fields['DELETE'].disabled = True
This code leverages the Field.disabled property added in Django 1.9. As the documentation says, "even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data," so you don't need to add more code to prevent deletion.
In your inline, you can add the flag can_delete=False
EG:
class MyInline(admin.TabularInline):
model = models.mymodel
can_delete = False
I found a very easy solution to quietly avoid unwanted deletion of some inlines. You can just override delete_forms property method.
This works not just on admin, but on regular inlines too.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
#property
def deleted_forms(self):
deleted_forms = super(MyInlineFormSet, self).deleted_forms
for i, form in enumerate(deleted_forms):
# Use form.instance to access object instance if needed
if some_criteria_to_prevent_deletion:
deleted_forms.pop(i)
return deleted_forms
I'm becoming increasingly bewildered by the range of answers on offer to the seemingly simple problem of adding custom fields to the django-registration register form/flow. This should be a default, documented aspect of the package (not to sound ungrateful, just that it is such a well-equipped package), but solutions to the problem are dizzying.
Can anyone give me the most simple solution to getting UserProfile model data included in the default registration register page?
Update:
I eventually used Django Registration's own signals to give me this hacky fix. It is particularly ugly because, I had to use try on the POST attribute dealing with my Boolean since I found that the checkbox returned nothing if left empty.
Would appreciate any advice on improving this, or best practice.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
Thanks
What you have looks like a workable approach.
I've looked through the django-registration code, and based on the following comments in the register view I've come up with another solution. I'm not totally sure this is cleaner, but if you aren't a fan of signals this is good. This also provides a much easier avenue if you intend to make more customizations.
# from registration.views.register:
"""
...
2. The form to use for account registration will be obtained by
calling the backend's ``get_form_class()`` method, passing the
``HttpRequest``. To override this, see the list of optional
arguments for this view (below).
3. If valid, the form's ``cleaned_data`` will be passed (as
keyword arguments, and along with the ``HttpRequest``) to the
backend's ``register()`` method, which should return the new
``User`` object.
...
"""
You could create a custom backend and override those mentioned methods:
# extend the provided form to get those fields and the validation for free
class CustomRegistrationForm(registration.forms.RegistrationForm):
receive_email = forms.BooleanField(initial=True, required=False)
# again, extend the default backend to get most of the functionality for free
class RegistrationBackend(registration.backends.default.DefaultBackend):
# provide your custom form to the registration view
def get_form_class(self, request):
return CustomRegistrationForm
# replace what you're doing in the signal handler here
def register(self, request, **kwargs):
new_user = super(RegistrationBackend, self).register(request, **kwargs)
# do your profile stuff here
# the form's cleaned_data is available as kwargs to this method
profile = new_user.userprofile
# use .get as a more concise alternative to try/except around [] access
profile.receive_email = kwargs.get('receive_email', False)
profile.save()
return new_user
To use the custom backend, you can then provide separate urls. Before including the default urls, write 2 confs that point at your custom backend. Urls are tested in the order defined, so if you define these two before including the defaults, these two will capture before the default ones are tested.
url(r'^accounts/activate/(?P<activation_key>\w+)/$',
activate,
{'backend': 'my.app.RegistrationBackend'},
name='registration_activate'),
url(r'^accounts/register/$',
register,
{'backend': 'my.app.RegistrationBackend'},
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
The docs actually describe all this, but they aren't particularly accessible (no readthedocs). They are all included in the project, and I was browsing them here.
I eventually used Django Registration's own signals to give me this fix.
I will clean up the try/except flow at some point. dokkaebi also points out above that I might be able to assess the request.GET parameters for when a checkbox is left empty.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
I do not care about concurrency issues.
It is relatively easy to build unique form field:
from django import forms
class UniqueUserEmailField(forms.CharField):
def clean(self, value):
self.check_uniqueness(super(UniqueUserEmailField, self).clean(value))
def check_uniqueness(self, value):
same_user = users.User.all().filter('email', value).get()
if same_user:
raise forms.ValidationError('%s already_registered' % value)
so one could add users on-the-fly. Editing existing user is tricky. This field would not allow to save user having other user email. At the same time it would not allow to save a user with the same email. What code do you use to put a field with uniqueness check into ModelForm?
quick and dirty way would be:
make check_uniqueness classmethod
use custom field check in ModelForm, like this:
class User(forms.ModelForm):
email = forms.EmailField()
def clean_email(self):
data = self.cleaned_data['email']
original = self.instance.email
if original == data:
return data
UniqueUserEmailField.check_uniqueness(data)
return data
better options?