Django admin - custom admin page for specific users - django

In my main url router I have two separate urls, one for each admin page that I set up in my admin.py file:
/adminmain/ URL goes to the default admin panel (admin.site.urls) with many different models registered.
/adminonly/ URL goes to a custom admin site (PostsAdmin2) and I want this site to work for users where is_admin = True in their user profile.
So far my admin.py file below isn't achieving this.
If I log in as a superuser then both urls will show the models registered to the respective admin page (this is fine).
But if I login as a non-superuser who IS an admin (is_admin = True) I just get a message: You don't have permission to view or edit anything. on BOTH pages. I want that user to see the models registered for the /adminonly/ page since I gave permission to do so in the PostsAdmin2 admin site.
My admin.py:
from django.contrib.admin import ModelAdmin, site, AdminSite
from .models import Post
class PostAdmin(ModelAdmin):
list_display = (
'id',
'slug',
'title',
'author',
'publish_date'
)
def has_view_permission(self, request, obj=None):
if request.user.is_admin:
return True
def has_add_permission(self, request):
if request.user.is_admin:
return True
def has_change_permission(self, request, obj=None):
if request.user.is_admin:
return True
def has_delete_permission(self, request, obj=None):
if request.user.is_admin:
return True
class PostsAdmin2(AdminSite):
site_header = 'Posts Admin'
def has_permission(self, request):
return request.user.is_admin
posts_site2 = PostsAdmin2(name='PostsAdmin')
#default admin page- only for superusers, /adminmain/ URL goes here
site.register(Post, PostAdmin)
site.register(..many more models and Admin classes registered)
#second admin page- meant for any user with is_admin = True, /adminonly/ URL goes here
posts_site2.register(Post, PostAdmin2)

Related

Django unit test to see if model is registered to admin page

I have a URL (/admin/) that goes to my Django admin panel (my URL router:url(r'^admin/', admin.site.urls),)
This admin panel includes an admin for the Post model.
That Post model is being registered by a ModelAdmin class called PostAdmin.
admin.py:
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = (
'id',
'slug',
'title',
'author'
)
admin.site.register(Post, PostAdmin)
Now I want to write a unit test for this admin.py file and I want to check that if you go to the /admin/ URL you see the Post Model.
How would you do this? I think it would be something like:
response = method_gets_url_content(`/admin/`)
assert.response.contains('Post') == True
But I'm not sure how to write a test like this since there is no method to get that URL (/admin).
One can work with Django's test client [Django-doc]:
from django.test import TestCase
from django.contrib.auth import get_user_model
class MyTests(TestCase):
#classmethod
def setUpTestData(cls):
self.user = get_user_model().objects.create_user(
username='foo',
password='bar'
)
def test1(self):
self.client.force_login(self.user)
response = self.client.get('/admin/')
self.assertContains(response, 'Tags')

Use autocomplete_fields with Proxy model

I want to implement autocomplete_fields feature but it doesn't work. I assume it happens because of Proxy model.
So I have Customer Proxy model and PromoCode model. PromoCode has FK to Customer model. And I need to have search field for customers in PromoCode change form. Here are models and admin classes:
class User(AbstractUser):
# bunch of fields
class Customer(User):
class Meta:
proxy = True
class CustomerAdmin(admin.ModelAdmin):
search_fields = ['email',]
admin.site.register(Customer, CustomerAdmin)
class PromoCode(TimeStampedModel):
customer = models.ForeignKey(User, on_delete=PROTECT, null=True, blank=True)
class PromoCodeAdmin(admin.ModelAdmin):
autocomplete_fields = ('customer',)
admin.site.register(PromoCode, PromoCodeAdmin)
This code gives error:
: (admin.E039) An admin for model "User" has to be registered to be referenced by PromoCodeAdmin.autocomplete_fields.
But I can't change model in customer field to Customer, becase when I run migration it breaks with following error:
ValueError: The field coupons.PromoCode.customer was declared with a lazy reference to 'users.customer', but app 'users' doesn't provide model 'customer'
Also I can't register User as admin class, because I don't need it to be registered. I register Customer model.
What can I do to solve such case?
It is not possible (see: https://code.djangoproject.com/ticket/30666). I got around this by registering the User admin, but making it redirect to my custom admin model. I also removed all of the actions on the user admin:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.urls import reverse
admin.site.unregister(User)
#admin.register(User)
class UserAdmin(BaseUserAdmin):
preserve_filters = False
def get_actions(self, request):
actions = super().get_actions(request)
if "delete_selected" in actions:
del actions["delete_selected"]
return actions
def has_delete_permission(self, request, obj=None):
return False
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def changelist_view(self, *args, **kwargs):
return HttpResponseRedirect(reverse("admin:core_domainuser_changelist"))

How to connect login model and post model

I have created a login and signup model also a post model in which a user can post the title and description. But when I login with a user and create a post then that post is also viewed by other user also.
I am using Django(python) with database sql.
For sign up form :
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
class UserCreateForm(UserCreationForm):
class Meta:
fields = ("username", "email", "password1", "password2")
model = get_user_model()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["username"].label = "Display name"
self.fields["email"].label = "Email address"
url.py :
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from . import views
app_name = 'accounts'
urlpatterns = [
url(r"login/$",auth_views.LoginView.as_view(template_name="accounts/login.html"),name='login'),
url(r"logout/$", auth_views.LogoutView.as_view(), name="logout"),
url(r"signup/$", views.SignUp.as_view(), name="signup"),
]
How can I limit my post to that specific user?
Well, its simple. You need to define a relation between post model and user model like this:
from django.contrib.auth import get_user_model
User = get_user_model()
class Post(models.Model):
created_by = models.ForeignKey(User)
# rest of the fields
Now in the Post Create View, you can do it like this:
from django.contrib.auth.mixins import LoginRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
# ...
def form_valid(self, form):
form.created_by = self.request.user
return super(PostCreateView, self).form_valid(form)
And finally in ListView, you need to define the queryset like this:
class PostListView(LoginRequiredMixin, ListView):
# ...
def get_queryset(self):
queryset = super(PostCreateView).get_queryset()
return queryset.filter(created_by=self.request.user)
Your problem is a concept called Authorization. you need to check authorization of your users on access to posts, so each user only sees his posts.
First of all save user beside post model on each instance (using ForeignKey). So that each post has a user assigned to it.
Now on accessing posts view with each user check if the user is authenticated, if so, then check if this user is the owner of that post or not, if not then give 403 permission denied error to him.
For example if your Post model is like this:
class Post(models.Model):
user = models.ForeignKey(User)
content = models.CharFie.........
.....
Then on each request to access a post, check if user is allowed to see it or not:
post = Post.objects.get(id=1) # For example you want to check post with id of 1
if post.user.id == request.user.id:
# Then allow user to see it,
else:
# Raise error 403
Note: Also add login_required decorator before post views. or IsAuthenticated permission class if you are using CBV.

Django admin : show records for respective user only

I have a simple model, say Resources. And I have fewer than 20 users and model admin serves the purpose to record requests.
Problem is that all users can see all records in model admin site.
Can this behaviour be changed to only show records created by same user only ?
Thank you in anticipation.
The django doc has an example that does almost exactly what you want:
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_queryset
The idea is to override the get_queryset() method in the model admin view:
# admin.py
from django.contrib import admin
class YourModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(MyModelAdmin, self).get_queryset(request)
return qs.filter(author=request.user)
admin.site.register(YourModel, YourModelAdmin)
You can adapt the queryset filter to even more specific needs at will.
UPDATE 2020:
Anyone who is curious as to what is the author field, then that is established in the models.py file of your app. For the admin.py part, you can visit the docs.
Step 1:
Make sure in your permissions you give access to the apps you want your users to have CRUD functionality over.
Step 2:
Admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
Models.py
from django.contrib.auth import get_user_model
class Lecture(models.Model):
author = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)

django admin readonly data (no edit, no add)

admin.py
class ChatMessageAdmin(admin.ModelAdmin):
def has_add_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
list_display = ('username','ip', 'timestamp','message',)
search_fields = ['message']
list_filter = ('username','ip')
admin.site.register(ChatMessage,ChatMessageAdmin)
when I rewrite has_change_permission method like upper I can't see the http://[domain]/admin/chat page which shows all the chat entries
How can I rewrite has_change_permission to have access to this page?
Edit
I don't the auth_user can edit the chat entry, but if he wants I want to have the ability to delete it.