Django admin site: how to change admin URL based on verbose_name - django

class Meta: verbose_name only changes its displayed name. But when its clicked. i want its url changed too based on verbose name
Here's my code:
Models:
tables = [f.id for f in StockId.objects.raw(
"SELECT TABLE_NAME AS id FROM INFORMATION_SCHEMA.tables WHERE TABLE_NAME LIKE 'my_project_auth_stockid%%'")]
table_map = {}
for tbl in tables:
class Stocks(models.Model):
user_id = models.IntegerField()
product_name = models.CharField(max_length=100)
mrp = models.IntegerField()
selling_price = models.IntegerField()
class Meta:
db_table = tbl
managed = False
verbose_name = _(tbl)
verbose_name_plural = _(tbl)
table_map[tbl] = Stocks
Admin.py
from my_project_auth.models import *
class AllStocksAdmin(AdminSite):
site_header = 'All User Stocks'
stockadmin = AllStocksAdmin(name="stockadmin")
for tbl in tables:
class StocksAdmin(ImportExportModelAdmin):
resource_class = StocksAdminResource
list_display = ["product_name"]
stockadmin.register(table_map[tbl], StocksAdmin)
What I am trying to do is in above to make multiple stock table out of single model. But Admin panel for each stocks is not working. All stocks table is pointing towards single table because of admin URL is based on model's class name.
I'm trying to do something like this in Admin.py, please suggest changes:
for tbl in tables:
class StocksAdmin(ImportExportModelAdmin):
resource_class = StocksAdminResource
list_display = ["product_name"]
def get_urls(self): # Not Working
urls = super(StocksAdmin, self).get_urls()
my_urls = path(f'stockadmin/{tbl}/', self.admin_view(stockadmin))
return my_urls + urls

To solve the problem of having multiple models with the same name we can generate then dynamically with unique names using the three argument form of type
from django.db import models
tables = [f.id for f in StockId.objects.raw(
"SELECT TABLE_NAME AS id FROM INFORMATION_SCHEMA.tables WHERE TABLE_NAME LIKE 'my_project_auth_stockid%%'")]
class StockBase(models.Model):
user_id = models.IntegerField()
product_name = models.CharField(max_length=100)
mrp = models.IntegerField()
selling_price = models.IntegerField()
class Meta:
abstract = True
for tbl in tables:
class Meta:
db_table = tbl
managed = False
verbose_name = tbl
verbose_name_plural = tbl
locals()[f'Stocks{tbl}'] = type(f'Stocks{tbl}', (StockBase,), {'Meta': Meta, '__module__': StockBase.__module__})
This will result in multiple models with the same fields, from the base class, and all with unique names.
I used __subclasses__ in the admin to get all the created models but it's pretty much the same, just register all the models.
from django.contrib import admin
from . import models
class StocksAdmin(admin.ModelAdmin):
list_display = ["product_name"]
for cls in models.StockBase.__subclasses__():
admin.site.register(cls, StocksAdmin)
Now your admin should be full of tens/hundreds/thousands of duplicate but named slightly different models

Related

django select records from multiple tables with same foreign key

I would like to execute a single query in Django which retrieves related data, by foreign key, in multiple tables. At present I have to run a query on each table e.g. (House, Furniture, People) using the House number as a filter.
In SQL I can do this in one query like this:
SELECT house.number, house.number_of_rooms, furniture.type, people.name
FROM (house INNER JOIN furniture ON house.number = furniture.house_number)
INNER JOIN people ON house.number = people.house_number
WHERE (((house.number)="21"));
Can this be done in Django?
See example models below:
class House(models.Model):
number = models.CharField('House Number', max_length=10, blank=True, unique=True, primary_key=True)
number_of_rooms = models.IntegerField(default=1, null=True)
class Furniture(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
In your models add related_name arguments for foreign keys, so that you can retrieve the objects related to the House() instance.
class Furniture(models.Model):
house_number = models.ForeignKey(House, related_name='house_furniture', on_delete=models.CASCADE, null=True)
type = models.CharField('Furniture Type', max_length=50)
class People(models.Model):
house_number = models.ForeignKey(House, related_name='house_people', on_delete=models.CASCADE, null=True)
first_name = models.CharField('First Name', max_length=50)
Then run the migration using following commands.
python manage.py makemigrations
python manage.py migrate
Then create a new serializers.py module in the same app.
#import models Furniture, People, house
from rest_framework import serializers
class FurnitureSerializer(serializer.ModelSerializer):
class Meta:
model = Furniture
fields = ['type'] # if you want all the fields of model than user '__all__'.
class PeopleSerializer(serializer.ModelSerializer):
class Meta:
model = People
fields = ['first_name'] # if you want all the fields of model than user '__all__'.
class HouseSerializer(serializer.ModelSerializer):
house_furniture = FurnitureSerializer(many=True)
house_people = PeopleSerializer(many=True)
class Meta:
model = Furniture
fields = ['number', 'number_of_rooms', 'house_furniture', 'house_people']
Now, in your views.py you can simply query on model House and serializer the result with HouseSerializer().
#import models from models.py
#import serializer from serializers.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import ListAPIView
class ListHouseView(ListAPIView):
serializer_class = HouseSerializer
queryset = House.objects.filter() #here you can apply filters on the fields of house model and user using related_name you can filter on other related models as well.
Now, simply call ad this in your app's urls.py
url_pattern = [
path('list-house/', ListHouseView.as_view()),
]
Make sure that have a path in your project's urls.py to reach this app's urls.py.
The usual Django way of dealing with this is Queryset.prefetch_related() and iterating through Python (unless you're using Postgres, which has its own solution of ArrayAgg). Given your models, it'll cost three queries, but you won't have to deal with de-normalized row results.
h = House.objects.prefetch_related('furniture_set', 'people_set').get(number='21')
for furniture in house.furniture_set.all():
print(furniture)
for person in house.people_set.all():
print(people)
prefetch_related() caches the results and does the "joining" in Python once the queryset is evaluated, so iterating through the reverse relationships won't incur additional queries, and you're free to structure/serialize the data however you like. The raw SQL from this is something like:
SELECT house.number, house.number_of_rooms FROM house WHERE house.number = '1'
SELECT furniture.id, furniture.house_number_id, furniture.type FROM furniture WHERE furniture.house_number_id IN ('1')
SELECT people.id, people.house_number_id, people.first_name FROM people WHERE people.house_number_id IN ('1')
But Django does that behind-the-scenes so that you can just deal with a model instance in Python.

Django queryset - column IN() GROUP BY HAVING COUNT DISTINCT

With the following models:
class Post(models.Model):
class Meta:
db_table = "posts"
class Tag(models.Model):
tag = models.CharField(max_length=50)
class Meta:
db_table = "tags"
class PostTag(models.Model):
postid = models.PositiveIntegerField()
tagid = models.PositiveIntegerField()
class Meta:
unique_together = ("postid", "tagid")
db_table = "posttags"
To get postids of posts which contain all the tagids given in TAGLIST where TAGLEN is the number of tagids in TAGLIST:
SELECT postid
FROM posttags
WHERE tagid IN (TAGLIST)
GROUP BY postid
HAVING COUNT(DISTINCT tagid) = TAGLEN
But how do I do this with Django ORM?
I found a solution.
TAGLEN = TAGLIST.count()
withtags = PostTag.objects.filter(tagid__in=TAGLIST)
withall = withtags.values("postid").annotate(tagtotal=Count("tagid", distinct=True)).order_by()
withall.filter(tagtotal=TAGLEN).values_list("postid", flat=True)
And running .query.__str__() on all this returns basically the following SQL below.
SELECT "postid"
FROM "posttags"
WHERE "tagid" IN (TAGLIST)
GROUP BY "postid"
HAVING COUNT(DISTINCT "tagid") = TAGLEN'

django not add auto increment primary key id when save an article

I hava an article app installed in django admin site,when i finish editing one article,I click the save button,but an error page:
article/models.py
# blog category models
class Category(models.Model):
id = models.IntegerField(primary_key=True,help_text='primary key')
name = models.CharField(max_length=50,help_text='category name')
description = models.TextField(default='',help_text='category description')
createtime = models.DateTimeField(auto_now_add=True)
modifytime = models.DateTimeField(auto_now=True)
categories = models.Manager()
class Meta:
db_table = 'article_category'
def __str__(self):
return self.name
#blog article models
class Article(models.Model):
STATUS = (
(0,'on'),
(1,'off')
)
id = models.IntegerField(primary_key=True,help_text='primary key')
category = models.ForeignKey(Category,help_text='foreigner key reference Category')
title = models.CharField(max_length=100,help_text='article title')
content = models.TextField(help_text='article content')
like = models.IntegerField(default=0,help_text='like numbers')
secretcode = models.CharField(max_length=512,help_text='who has the code can scan')
status = models.IntegerField(choices=STATUS,help_text='status of the article')
createtime = models.DateTimeField(auto_now_add=True,help_text='time that first created')
modifytime = models.DateTimeField(auto_now=True,help_text='time when modified')
articles = models.Manager()
class Meta:
db_table = 'article'
article/widgets.py
from pagedown.widgets import AdminPagedownWidget
from django import forms
from .models import Article
class ArticleModelForm(forms.ModelForm):
content = forms.CharField(widget=AdminPagedownWidget())
class Meta:
model = Article
fields = ('title','category', 'content', 'secretcode', 'status')
article/admin.py
from django.contrib import admin
from .widgets import ArticleModelForm
from .models import Article,ArticleImage,Category
class MMBArticleAdmin(admin.ModelAdmin):
form = ArticleModelForm
admin.site.register(Article,MMBArticleAdmin)
admin.site.register(Category)
admin.site.register(ArticleImage)
the page in the admin site looks likeļ¼š
and then I click save ,the error page show up like above!why did this happen?and how to fix it?
You've overridden the default automatic field with a manual non-autoincrementing ID. Don't do that. Remove your id fields altogether.

Multiple ManyToMany in Django Admin

Problem relating to driving licences. A licence has a named person who is assiged multiple categories of driving skills. Each category of driving skill has a single renewal date and possible multiple related restrictions
Name Lic Cat Renewal date Restrictions
Tommy AM 12/05/15 44.01, 101, 999
Tommy E+B 14/12/16
Tommy C 18/04/18 101
TG E+C 14/12/16 90, 101, 999,
TG B 18/04/18 105
models.py
class Employee(models.Model):
name = models.CharField(max_length=75)
class DrivingLicenceCategory(models.Model):
name = models.CharField(max_length=11)
class IssuingBody(models.Model):
name = models.CharField(max_length=75)
class DrivingLicence(models.Model):
employee_id = models.ForeignKey(Employee)
driver_number = models.CharField(max_length=49)
permit_number = models.CharField(max_length=49)
issuing_body = models.ForeignKey(IssuingBody)
expiry_date = models.DateField()
class DrivingLicenceRestrictions(models.Model):
name = models.CharField(max_length=21)
description = models.CharField(max_length=1024, blank=True, null=True)
class DrivingLicenceCategoryRestrictions(models.Model):
licence_category = models.ForeignKey(DrivingLicenceCategory)
licence_restriction = models.ForeignKey(DrivingLicenceRestrictions)
expiry_date = models.DateField()
class DrivingLicenceDetail(models.Model):
driving_licence = models.ForeignKey(DrivingLicence)
category_restriction = models.ManyToManyField(DrivingLicenceCategoryRestrictions)
admin.py
from django.contrib import admin
from drvlic.models import *
class DrvLicIssuingBodyAdmin(admin.ModelAdmin):
search_fields = ('^name', )
class DrvLicCategoryAdmin(admin.ModelAdmin):
search_fields = ('^name',)
class DrvLicRestrictionsAdmin(admin.ModelAdmin):
search_fields = ('^name', )
list_display = ('id', 'name', 'description',)
class DrvLicLicenceAdmin(admin.ModelAdmin):
search_fields = ('^employee',)
class DrvLicCategoryRestrictionsAdmin(admin.ModelAdmin):
search_fields = ('^employee',)
filter_horizontal = ('licence_category', 'licence_restriction')
admin.site.register(DrvLicIssuingBody, DrvLicIssuingBodyAdmin)
admin.site.register(DrvLicCategory, DrvLicCategoryAdmin)
admin.site.register(DrvLicRestrictions, DrvLicRestrictionsAdmin)
admin.site.register(DrvLicLicence, DrvLicLicenceAdmin)
admin.site.register(DrvLicCategoryRestrictions, DrvLicCategoryRestrictionsAdmin)
Is my model correct to produce the table above in Django Admin or should the model be different? How is admin.py set up to allow access and enter the data?
Django is telling me that 'DrvLicCategoryRestrictionsAdmin.filter_horizontal[0]' must be a ManyToManyField'. This is irrevelent if the models.py are constructed incorrectly, I think.
You don't need the '^' for the search field. Just search_fields = 'name' should suffice.
To display a ManyToManyField, you need to loop through all associated objects. But, even before that point, I think you need to restructure your models to have one 'all encompassing' model that holds that table you are trying to produce. So, something like:
models.py
class License(models.Model):
employee = models.ForeignKey(Employee)
category = models.ManyToMany(DrivingLicenceCategory)
restriction = models.ManyToMany(DrivingLicenceRestrictions)
expiry_date = models.DateField()
def get_categories(self):
return ",<br>".join([a.name for a in self.category.all()])
def get_restrictions(self):
return ",<br>".join([a.name for a in self.restriction.all()])
admin.py
class LicenseAdmin(admin.ModelAdmin):
list_display = ('employee', 'get_categories', 'get_restrictions', 'expirary_date')

Django-tables2: Change text displayed in column title

I am working with a MySQL view (Create View as Select ...) and have successfully manged to connect the view to a model like this:
#models.py
class Dashboard(models.Model):
devenv = models.CharField(max_length=30, primary_key=True)
numberofissues = models.BigIntegerField()
class Meta:
managed=False
db_table = 'stability_dashboard'
I have also managed to display data in a table using the boiler plate code from the example:
#tables.py
class DashboardTable(tables.Table):
class Meta:
model = Dashboard
attrs = {'class': 'paleblue'}
#views.py
def dashboard(request):
table = DashboardTable(Dashboard.objects.all())
RequestConfig(request).configure(table)
return render(request, 'uptime/dash.html', {'table': table})
I would now like to change the title displayed in each column to something more understandable including spaces e.g. Instead of 'devenv' => 'Development Environment'
Just add the columns whose names you want to override in your tables.py. For instance
#tables.py
import django_tables2 as tables
from models import Dashboard
class DashboardTable(tables.Table):
devenv = tables.Column(verbose_name= 'Development Environment' )
class Meta:
model = Dashboard
attrs = {'class': 'paleblue'}
Another (probably more DRY) solution is to leave tables.py as is and add verbose_name in your model definition:
#models.py
class Dashboard(models.Model):
devenv = models.CharField(max_length=30, primary_key=True, verbose_name='Development Environment')
numberofissues = models.BigIntegerField(verbose_name='Number of Issues')
class Meta:
managed=False
db_table = 'stability_dashboard'