How to make a TabularInline orderable by user? - python

I created an admin view using the TabularInline class. With this view it is not possible to let the user order/sort the table to their liking, like within a standard ModelAdmin view. I would like to achieve this. I am using Django 2.2.1.
What I tried
I searched the Django docs, but besides hard coding 'ordering' within the admin.py file, I couldn't find anything on this topic. This is not what I want, I want the admin user to choose how to order.
I tried to use adminsortable2, however I ran into some issues I couldn't resolve. This got me thinking: is it really not possible with the standard Django package.
My code
My model consists of TimeFrames, which are made up by TimeSlots.
This is what my model.py looks like:
from django.db import models
class TimeFrame(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class TimeSlot(models.Model):
DAY_CHOICES = ('work', 'Work'), ('weekend', 'Weekend')
begin_time = models.TimeField()
end_time = models.TimeField()
reference = models.CharField(max_length=30)
day = models.CharField(max_length=30, choices=DAY_CHOICES)
timeframe = models.ForeignKey('TimeFrame', on_delete=models.CASCADE)
I don't want to register a separate TimeSlot admin view, since the TimeSlots are always part of a TimeFrame. They don't exist on their own.
from django.contrib import admin
from .models import *
class TimeSlotInline(admin.TabularInline):
model = TimeSlot
ordering = ("day", "begin_time") #hard-coded ordering
#admin.register(TimeFrame)
class TimeFrameAdmin(admin.ModelAdmin):
inlines = [TimeSlotInline]
Expected outcome
Enable the admin user to sort like in a standard ModelAdmin view.

Related

Django Administration - Customize list appearance and selection of ManyToManyField keys

I have a database with 1000+ songs. I have a custom model "Schedule" that accepts songs as field.
models.py
from django.db import models
class Song(models.Model):
title = models.CharField(max_length=255)
words = models.TextField()
slug = models.SlugField()
date = models.DateTimeField(auto_now_add=True)
snippet = models.CharField(max_length=50)
def __str__(self):
return self.title
class Schedule(models.Model):
songs = models.ManyToManyField(Song)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.date)
admin.py
from django.contrib import admin
from .models import Song, Schedule
#admin.register(Song)
class SongModel(admin.ModelAdmin):
search_fields = ('title',)
list_display = ('title',)
list_per_page = 100
#admin.register(Schedule)
class ScheduleModel(admin.ModelAdmin):
search_fields = ('date',)
list_display = ('date',)
list_per_page = 100
I want to be able to add any song I want to a schedule, but it is difficult do to so through the default list in the Django-Administration, which looks like this. I have to scroll and CTRL+select each one of them, then add them.
I'd like something more more practical where I can select, search, etc.
What are my options? I don't know where to start looking.
Option 1
It's only comfortable if you have very few related items (few songs in schedule). But it is super easy and will be better than what you have now. (django.contrib.admin comes with built-in select2.)
#admin.register(Schedule)
class ScheduleAdmin(admin.ModelAdmin):
...
autocomplete_fields = ("songs",)
Option 2
(upd: damn, forgot it at first, it's also super simple and quite efficient)
It looks alright-ish. Usable. Not particularly comfortable. But better than ctrl-clicking the stuff.
#admin.register(Schedule)
class ScheduleAdmin(admin.ModelAdmin):
...
filter_horizontal = ('songs',)
Option 3
If you want a comfortable UI without implementing custom pages or actions (which are unfortunately a complete mess), you should use a StackedInline admin.
It's quite a bit more difficult though.
First you will need a through model. (I don't think inlines are possible with auto-generated many-to-many models.) It's basucally your many-to-many between the two models. Something like that:
class ScheduleSongNM(models.Model):
song = models.ForeignKey("Song", null=False)
schedule = models.ForeignKey("Schedule", null=False)
Tell your Schedule model to use your custom through model:
class Schedule(models.Model):
songs = models.ManyToManyField(Song, through="ScheduleSongNM")
Now create an inline admin for the ScheduleSongNM:
class ScheduleSongInline(admin.StackedInline):
model = ScheduleSongNM
fields = ["song"]
autocomplete_fields = ["song"] # select2 works here too
Finally, tell your Schedule admin that it has an inline now:
#admin.register(Schedule)
class ScheduleAdmin(admin.ModelAdmin):
...
inlines = [ScheduleSongInline]
...
Maybe I missed something (obviously I haven't tested it), but I think you got the general idea. In the end you get a box inside of your Schedule admin that looks something like that (plus auto completion for song names):

How can I allow user to be "sloppy" when entering a Django phone number?

I'm making a Django form to update users membership to a website. I want to be able to store phone numbers. I found django-phonenumber-field which is great. The only problem is that the form I created for the user to enter their phone number is too specific. If the user doesn't enter their number as "+99999999" then they get an input error. I would like for the user to be able to enter their number a variety of ways: 999-999-9999, 9-999-9999, (999)999-9999, etc. What's the best way to accomplish this?
My code:
models.py
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
class Member(models.Model):
"""defines a member for annual registration"""
name = models.CharField(max_length=255)
mailing_address = models.CharField(max_length=255)
home_phone = PhoneNumberField()
other_phone = PhoneNumberField(blank=True)
email = models.EmailField()
forms.py
from django import forms
from .models import Member
class MembershipForm(forms.ModelForm):
"""form for renewing membership"""
class Meta:
model = Member
fields = ('name',
'mailing_address',
'home_phone',
'other_phone',
'email',
)
Thank you for any help!
That field definition is a custom field in Django. You can create your own custom fields. I would recommend getting the code for the PhoneNumberField, which is open source, and subclassing it to you own field MyPhoneNumberField. Override the validation logic to be as you wish. See the details of how these things work at
https://docs.djangoproject.com/en/1.11/ref/forms/validation/

How to authenticate user for a specific object rather than whole class in django?

I am working on making an app to add clubs in website. This is my model.py file
from django.db import models
from stdimage import StdImageField
# Create your models here.
class Club(models.Model):
ClubName = models.CharField(max_length=200)
ClubLogo = StdImageField(upload_to='club_logo', variations={'thumbnail':(150, 200, True)})
ClubDetails = models.TextField()
ClubStartDate = models.DateField()
def __str__(self):
return self.ClubName
class Notice(models.Model):
NOTICE = 'NOTICE'
UPDATES = 'UPDATES'
EVENTS = 'EVENTS'
NOTICE_IN_CHOICES = (
(NOTICE, 'Notice'),
(UPDATES, 'Updates'),
(EVENTS, 'Events'),)
NoticeType = models.CharField(
max_length=20, choices=NOTICE_IN_CHOICES, default=NOTICE)
NoticeTag = models.CharField(max_length=30)
NoticeStartDate = models.DateField(auto_now_add=True)
NoticeEndDate = models.DateField()
NoticeFile = models.FileField(default='#', upload_to='notice/%Y/%m/%d')
NoticeContent = models.TextField(default='NA')
NoticeClub = models.ForeignKey(Club)
def __str__(self):
return self.NoticeTag
class Members(models.Model):
MemeberName = models.CharField(max_length=200)
MemberImage = StdImageField(upload_to='member_photo', variations={'thumbnail':(150, 120, True)})
MemberEmail = models.EmailField()
MemberClub = models.ForeignKey(Club)
def __str__(self):
return self.MemeberName
Now when i am making users via django's inbuilt admin panel i have option to give permission to users to change member of any club but i want to give access to change members of only that particular club which he is member of.
As you can see in this picture that all club are in dropdown option when someone who has access to add notices adding otices. But instead of that i want only one option in the dropdown for the useradmin to which he is associated.
this is my admin.py file
from django.contrib import admin
# Register your models here.
from club.models import Club, Members, Notice
admin.site.register(Club),
admin.site.register(Members),
admin.site.register(Notice),
This is a problem with which many users have been struggling with.
I have been using couple of external packages, and couple of self made solutions. But the best one I have found so far is Django Guardian It's an implementation of per object permission .This means you can manage users and permissions to which they have access to.

Filtering cities by country in the django admin edit form

I'm using django-cities on a small project that lists locations.
The locations model is this:
class Location(models.Model):
def __unicode__(self):
return self.name
name = models.CharField(max_length=200)
instagram_id = models.IntegerField(default=0)
country = models.CharField(max_length=200)
city = models.CharField(max_length=200)
new_city = models.ForeignKey(City, related_name='venuecity', null=True, blank=True)
latitude = models.DecimalField(max_digits=17, decimal_places=14, default=0)
longitude = models.DecimalField(max_digits=17, decimal_places=14, default=0)
the 'city' and 'new_city' fields are temporary while I migrate the data.
My problem is on the admin side of things, how can I filter the city select field by country? The goal is to make it easier for the user to select the correct city when he is adding a new record or editing an existing one.
I looked through django's admin documentation but couldn't figure out how to do this.
You can customize the form used by the Django admin. In your app, create a form that exhibits the behavior you want. In this case you will probably want to write one that overloads the form's __init__() method and adjusts the values of your city field dynamically. E.g.:
class LocationAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super(LocationAdminForm, self).__init__(*args, **kwargs)
if self.instance:
# we're operating on an existing object, not a new one...
country = self.instance.country
cities = # get cities from your master countries list...
self.fields["new_city"] = ChoiceField(choices=cities)
Once you have a working ModelForm, you can tell the admin to use it in your admin.py:
class LocationAdmin(admin.ModelAdmin):
form = LocationAdminForm
The admin customization is documented here. An old but still mostly relevant article on dynamic Django forms is here.
To do this for NEW records, you will need to implement most of it on the front-end. You will need Javascript code that retrieves and updates the cities list dynamically as a different country is selected. Your Django app would expose an AJAX view the Javascript can call. It would pass the country and return a simple JSON object of cities, which it would use to update the element in the form.
Alternatively, a pure server-side solution is possible if you change the workflow. This could work like a "wizard" and ask the user to first select the country. Click next to view step 2 of the wizard with a dynamically generated Django form as above. The Django wizard app may be of use.
I tried the above solution (#jdl2003) but it didn't work (at least in Django 1.8) and I was getting the same error as #bruno-amaral mentioned. I was able to fix the issue like this:
#forms.py
from django.forms import ModelForm, ModelChoiceField
from cities_light.models import City
class MyUserAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyUserAdminForm, self).__init__(*args, **kwargs)
if self.instance:
country = self.instance.country
cities = country.city_set.all() if country else City.objects.none()
self.fields['city'] = ModelChoiceField(queryset=cities)
#admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from myapp.forms import MyUserAdminForm
class MyUserAdmin(UserAdmin):
form = MyUserAdminForm
UserAdmin.fieldsets += (
(None, {'fields': ('dob', 'country', 'city')}),
)
UserAdmin.list_display += ('country',)
admin.site.register(get_user_model(), MyUserAdmin)
Only issue I have now, is that cities list is not getting updated dynamically. I need to first save the country field and after that cities are updated.

How to make django admin site to not recognize a foreign key'ed model (on different app) to be a model of current app?

I have three models - two of them are in one app, and the third one is on the another. The structure is like this:
taapp.models:
class Teachers(model.Model):
fullname = models.CharField(max_length=50)
...
class TeachersScale(model.Model):
teacher = models.ForeignKey("Teachers")
abbr = models.ForeignKey("questions.QuestionTypes")
questions.models:
class QuestionTypes(models.Model):
abbr = models.CharField(max_length=5)
......
I registered all these models to admin:
taapp.admin:
from taapp.models import Teachers
from taapp.models import TeachersScale
from django.contrib import admin
from admin_forms import TeachersAdmin, TeachersScaleAdmin
admin.site.register(Teachers, TeachersAdmin)
admin.site.register(TeachersScale, TeachersScaleAdmin)
taapp.admin_forms:
from django import forms
from django.contrib import admin
class TeachersAdmin(admin.ModelAdmin):
list_display = ('fullname', 'email', 'registration_date')
class TeachersScaleAdmin(admin.ModelAdmin):
list_display = ('teacher', 'abbr')
list_filter = ['teacher','abbr']
When I try to add a field to TeachersScale in admin site, I get the following error:
DatabaseError at /admin/taapp/teachersscale/add/
(1146, "Table 'taapp.questions_questiontypes' doesn't exist")
It treats QuestionTypes, as it is a model in taapp. How to solve it? Or is there something wrong with my db design?
I tried TabularInline for QuestionTypes to see if reverse adding works. Well, it works:
questions.admin:
class TeachersScaleInline(admin.TabularInline):
model = TeachersScale
class QuestionTypesAdmin(admin.ModelAdmin):
inlines = [TeachersScaleInline]
Thanks in advance.
It looks like you haven't actually created your questions table, or if you have you've forced it into a different database. Foreign keys expect to share the same database, and it's perfectly standard to have multiple apps sharing the same database. That's why the app name is part of the automatically generated table name.

Categories