One admin for multiple sites - django

I have two sites with different SITE_IDs, but I want to have only one admin interface for both sites.
I have a model, which is just an extended FlatPage:
# models.py
class SFlatPage(FlatPage):
currobjects = CurrentSiteManager('sites')
galleries = models.ManyToManyField(Gallery)
# etc
# admin.py
class SFlatPageAdmin(FlatPageAdmin):
fieldsets = None
admin.site.register(SFlatPage, SFlatPageAdmin)
admin.site.unregister(FlatPage)
I don't know why, but there are only pages for current site in admin interface. On http://site1.com/admin/ I see flatpages for site1, on http://site2.com/admin/ I see flatpages for site2. But I want to see all pages in http://site1.com/admin/ interface! What am I doing wrong?

It's because of CurrentSiteManager. According to the documentation "it's a model manager that automatically filters its queries to include only objects associated with the current Site."
Remove the line and everyting should work as expected. Or if you make use of currobjects somewhere else in your code note that admin interface always use the first manager specified, so you need to specify the standard manager first, like this:
# models.py
class SFlatPage(FlatPage):
objects = models.Manager() # Is first, so will be used by admin
currobjects = CurrentSiteManager('sites') # your alternative manager
galleries = models.ManyToManyField(Gallery)

Related

Django: Custom default Manager with user based queries for all views/interfaces (custom, admin, graphene, rest, ...)

In Django I'm trying to implement some kind of a "security middleware", which gives access to certain db information only, if the logged in user matches.
Up to now I found two approaches: middleware or custom function in manager (Both explained here Django custom managers - how do I return only objects created by the logged-in user?).
Example for cleanest solution: custom function
class UserContactManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(creator=user)
class MyUser(models.Model):
# Managers
objects = UserContactManager()
# then use it like this in views
data = MyUser.objects.for_user(request.user)
However, this solution only works, if you have control over the code which invokes this custom function (here: for_user()).
But if you are using third parties apps like Django-REST, Graphene or AdminViews, they don't have the possibility to configure a specific query-func to use.
My goal:
Replace the default Manager
Add user-based filters to query_set
Use the model as normal in all app configurations
Idea (pseudo code!)
from django.x import current_user
class UserBasedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author=current_user.name)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = UserBasedManager() # The default manager.
Now I could use the model Book as normal in all extensions.
I know this solution would have some drawbacks, which are okay for me:
Can be only used when user is available (e.g. no direct script support)
Handling no or anonymous user is missing (left it away to keep example short)
My use case
I have a project, which shall give access to data by different interfaces (admin pages, custom views, rest, graphql).
As I have so many interfaces, I don't want to implement the access-rights in the views. This would cost too much time and is hard to maintain and the risk for security problems in one specific view/interface is too high.
Let's say I'm storing commits of git repositories in a database.
And the user shall get access to all commits, which are part of repos the user has READ access.
The question is: How can I implement this as a generic, view/interface independent solution?
Install django-threadlocals package and call get_current_user function to get the current user
from threadlocals.threadlocals import get_current_user
class UserBasedManager(models.Manager):
def get_queryset(self):
current_user = get_current_user()
return super().get_queryset().filter(author=current_user.name)

Django : proper way to make website title editable in admin

I'm building a website with Django. I have a main title for the website in my header template, currently hard-coded in it.
How can I make it editable in the admin by the website administrator (or any user with the right credentials) ? Ideally I'd also like to be able to make more such site-wide (or app-wide) attributes editable in the admin (such as site - or app- description, for instance).
I have in mind something like WordPress' bloginfo() . A practical example of this is washingtonpost.com who changed their moto in their header to "Democracy dies in darkness" a few weeks ago.
Of course once the title (or any other attribute) has been edited in the admin I need to be able to get it from within my template.
You can create a simple model to store dynamic website parameters like this for example:
class WebsiteParam(models.Model):
key = models.CharField(max_length=50)
val = models.CharField(max_length=1024)
Then define custom template context processor settings_processor.py
def settings_processor(request):
vals = {x.key: v.val for x in WebsiteParam.objects.all()}
return {'website_settings': vals}
Add this processor into your django settings.py something like:
from django.conf import global_settings
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
"myapp.settings_processor.settings_processor",
)
And you will be able to use your settings in all of your templates like
<html><head><title>{{website_settings.title}}</title>...
if you have settings with title key added into the database
You should of course add caching into request context processor and other conditions if necessary

Dynamically created proxy models do not have listed permissions on admin site

Here is my models.py:
class Item(models.Model):
# ... some irrelevent fields ...
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_lenght=30)
category_id = models.IntegerField()
Tag is actually a general-purpose name. Each item has many different type of tags - currently there are four types: team tags, subject tags, admin tags and special tags. eventually there will probably be a few more.
The idea is, they all have basically the same fields, so instead of having like 4 tables with manytomany relationship, and instead of adding a new column for Item whenever adding a new type, everything is called a 'tag' and it's very easy to add new types without any change to the schema.
Now to handle this in the admin.py I'm using dyamically created proxy models (based on this), as such:
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
class TagAdmin(models.Model):
def queryset(self):
return self.model.objects.filter(category_id = self.cid)
class TeamAdmin(TagAdmin):
cid = 1
class SubjectAdmin(TagAdmin):
cid = 2
# ... and so on ...
create_modeladmin(TeamAdmin, name='Teams', model=Tag)
create_modeladmin(SubjectAdmin, name='Subject', model=Tag)
#... and so on ...
This works great for me. However, different staff members need different editing permissions - one guy shouldn't access admin tags, while the other should only have access to edit subject-tags and team-tags. But as far as the admin site is concerned - the dynamic models do not exist in the permission list, and I can't give anyone permissions regarding them.
i.e. a user given all permissions on the list will still not have access to edit any of the dynamic models, and the only way to let anyone access them at all is to give him a superuser which obviously defies the point
I searched SO and the web and I can't anyone with a similar problem, and the docs don't say anything about this not in the dynamic models section or the proxy models section. so I'm guessing this is a different kind of problem. Any help would be greatly appreciated
UPDATE
So after some research into it, the answer was simple enough. Since permissions in django are objects that are saved to the database, what I needed to do was simple - add the relevent permissions (and create new ContentType objects as well) to the db, and then I could give people specific pemissions.
However, this raised a new question - is it a good convention to put the function that creates the permissions inside create_modeladmin as a find_or_create sort of function (that basically runs every time) or should it be used as an external script that I should run once every time I add a new dynamic model (sort of like how syncdb does it)?
And is there a way to also create the permissions dynamically (which seems to me like the ideal and most fitting solution)?
of course you can create permissions, django have django.contrib.auth.management.create_permissions to do this

Custom Django Inline Admin

Let's say I have these two models:
class Egg(models.Model):
# some fields
class Spam(models.Model):
egg = models.ForeignKey(Egg)
img = models.ImageField()
I planned to have the spams inlined to egg in admin site. The problem is I also want a very customized method on uploading the spam images (like this), like having my own view and template. So far, I just got :
class CustomInline(admin.StackedInline):
model = Spam
template = 'admin/app/inline.html' # empty
class EggAdmin(admin.ModelAdmin):
inlines = [CustomInline, ]
The idea is having some kind of gallery of spams and custom image upload in egg admin. (Is this achievable?)
So the questions are:
I want to injects variables to be available on the template (spam objects on inline.html for gallery) . Is there a way to do this?
Is it okay to POST something to a view (upload process)? Or that particular view must be registered first on admin site or something?
I've looked on InlineAdmin source but still have no idea what to do/override
Thanks
using the form property you can subclass your ModelForm and completely change the way your inline form works.

Multiple images per Model

I'm writing a simple real-estate listing app in Django. Each property needs to have a variable number of images. Images need to have an editable order. And I need to make the admin user-proof.
So that said, what are my options?
Is there a ImageList field that I don't know about?
Is there an app like django.contrib.comments that does the job for me?
If I have to write it myself, how would I go about making the admin-side decent? I'm imagining something a lot slicker than what ImageField provides, with some drag'n'drop for re-ordering. But I'm a complete clutz at writing admin pages =(
Variable lists, also known as a many-to-one relationship, are usually handled by making a separate model for the many and, in that model, using a ForeignKey to the "one".
There isn't an app like this in django.contrib, but there are several external projects you can use, e.g. django-photologue which even has some support for viewing the images in the admin.
The admin site can't be made "user proof", it should only be used by trusted users. Given this, the way to make your admin site decent would be to define a ModelAdmin for your property and then inline the photos (inline documentation).
So, to give you some quick drafts, everything would look something like this:
# models.py
class Property(models.Model):
address = models.TextField()
...
class PropertyImage(models.Model):
property = models.ForeignKey(Property, related_name='images')
image = models.ImageField()
and:
# admin.py
class PropertyImageInline(admin.TabularInline):
model = PropertyImage
extra = 3
class PropertyAdmin(admin.ModelAdmin):
inlines = [ PropertyImageInline, ]
admin.site.register(Property, PropertyAdmin)
The reason for using the related_name argument on the ForeignKey is so your queries will be more readable, e.g. in this case you can do something like this in your view:
property = Property.objects.get(pk=1)
image_list = property.images.all()
EDIT: forgot to mention, you can then implement drag-and-drop ordering in the admin using Simon Willison's snippet Orderable inlines using drag and drop with jQuery UI
Write an Image model that has a ForeignKey to your Property model. Quite probably, you'll have some other fields that belong to the image and not to the Property.
I'm currently making the same thing and I faced the same issue.
After I researched for a while, I decided to use django-imaging. It has a nice Ajax feature, images can be uploaded on the same page as the model Insert page, and can be editable. However, it is lacking support for non-JPEG extension.
There is a package named django-galleryfield. I think it will meet your demand.