Django Admin ManyToManyField - django

I've made a model (models.py):
class opetest(models.Model):
name = models.CharField(max_length=200)
author = models.ForeignKey(User, related_name='author')
description = models.TextField(u'Test description', help_text = u'Some words about quiz')
pub_date = models.DateTimeField('date published', blank=False)
vacancies = models.ManyToManyField(Vacancy, blank=True)
students = models.ManyToManyField(User, blank=True, related_name='opetests') #This field I want to edit on "User change page"
estimate = models.IntegerField(default = 0, help_text = u'Estimate time in hours. \'0\' - unlimited')
then I try to add inline block to allow assign opetest on 'change user' page (admin.py):
class ProfileAdmin(UserAdmin):
filter_horizontal = ('opetests',)
admin.site.unregister(User)
admin.site.register(User, ProfileAdmin)
And I got an error:
'ProfileAdmin.filter_horizontal' refers to field 'opetests' that is missing from model 'User'.
I want to show opetests like Groups on change user page. How can I achieve that?

Hmm, I don't think you want inlines here.
You want to be using the Django admin's filter_horizontal:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_horizontal
class ProfileAdmin(UserAdmin)
filter_horizontal = ('opetest',)
That will give you the widget that you're describing, used to add/remove Groups on the User Change page.
Ok, based on your edits, updated answer - basically, what we have is a UserProfile, linked to each user.
The UserProfile contains a m2m relationship to opetest - which we show in the admin with a filter_horizontal. End result is something like this:
models.py
from django.db import models
from django.contrib.auth.models import User
class opetest(models.Model):
name = models.CharField(max_length=200)
author = models.ForeignKey(User, related_name='author')
description = models.TextField(u'Test description', help_text = u'Some words about quiz')
pub_date = models.DateTimeField('date published', blank=False)
#vacancies = models.ManyToManyField(Vacancy, blank=True)
students = models.ManyToManyField(User, blank=True, related_name='opetests') #This field I want to edit on "User change page"
estimate = models.IntegerField(default = 0, help_text = u'Estimate time in hours. \'0\' - unlimited')
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
ope = models.ManyToManyField(opetest)
test_flag = models.BooleanField()
admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from secondapp.models import UserProfile, opetest
admin.site.unregister(User)
class opetestAdmin(admin.ModelAdmin):
pass
class UserProfileInline(admin.StackedInline):
model = UserProfile
filter_horizontal = ('ope',)
class CustomUserAdmin(UserAdmin):
#filter_horizontal = ('user_permissions', 'groups', 'ope')
save_on_top = True
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'last_login')
inlines = [UserProfileInline]
admin.site.register(User, CustomUserAdmin)
admin.site.register(opetest, opetestAdmin)
Let me know if you have any questions, or need anything further.

Related

Automatically search all fields for all models in django admin

I want to be able to search all models for all fields in Django admin, without having to setup ModelAdmin and searchfields individually.
example:
I have all my models in model.py:
# This is an auto-generated Django model module.
from django.db import models
class Diagnosis(models.Model):
id = models.BigAutoField(primary_key=True)
code = models.CharField(max_length=255)
starting_node = models.ForeignKey('Node', models.DO_NOTHING, blank=True, null=True)
class Meta:
managed = False
db_table = 'diagnosis'
def __str__(self):
return 'Diag #' + str(self.id) + ' - ' + self.code
class DiagnosisHistory(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=255, blank=True, null=True)
date = models.DateTimeField(blank=True, null=True)
id_user = models.TextField(blank=True, null=True)
report = models.TextField(blank=True, null=True)
json_report = models.TextField(blank=True, null=True)
vin = models.CharField(max_length=20, blank=True, null=True)
class Meta:
managed = False
db_table = 'diagnosis_history'
# and so on
and the admin.py where I register the models:
from django.contrib import admin
from . import models
# Do not care. Register everything
for cls in [cls for name, cls in models.__dict__.items() if isinstance(cls, type)]:
admin.site.register(cls)
I don't want to run through each Model and manually create a ModelAdmin with each field
This is the solution I came up with:
from django.contrib import admin
from django.db import models as django_models
from . import models
relationship_fields = (django_models.ManyToManyField, django_models.ForeignKey, django_models.OneToOneField)
for cls in [cls for name, cls in models.__dict__.items() if isinstance(cls, type)]:
meta_fields = [field.name for field in cls._meta.local_fields if not isinstance(field, relationship_fields)]
class Admin(admin.ModelAdmin):
search_fields = meta_fields
admin.site.register(cls, Admin)
Note: registering all fields will fail since some are relationships. using cls._meta.local_fields exclude inferred relationships but you also need to exclude fields such as foreign keys defined in your model. Thus, we filter with isinstance(field, relationship_fields)
Note 2: I should probably use get_fields since local_fields seems to be private API (see https://docs.djangoproject.com/en/3.1/ref/models/meta/)
class MyModelAdmin(admin.ModelAdmin):
# ...
search_fields = [field.name for field in MyModel._meta.fields]
# ...

how to make a form field exact?

In django how to make form field exact, i.e it will have choices?
My forms.py:
from django import forms
class FilterForm(forms.Form):
category = forms.CharField()
price = forms.IntegerField()
My models.py:
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
class Item(models.Model):
title = models.CharField(max_length=120)
price = models.FloatField()
discount_price = models.FloatField(blank=True, null=True)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=120, null=True, blank=True)
image_url = models.CharField(max_length=2083, null=True, blank=True)
slug = models.SlugField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
Please make use of a ModelForm [Django-doc]. A ModelForm is capable of automating a lot of aspects when creating or update model records. Furthermore it can automatically construct the fields based on the fields of the model. You can, if you want to, alter the widgets, etc. But usually a ModeLField is a good starting point.
Here you thus can construct a form like:
# app/forms.py
from django import forms
from app.models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = ['category', 'price']
Where you replace app with the name of the app.
You can use ModelForm
from django import forms
from .models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = [
'category',
'price'
]
If you wanna stick with Form, use Choice Field and copy the Choices in form
from django import forms
class FilterForm(forms.Form):
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
category = forms.ChoiceField(choices=CATEGORY_CHOICES)
price = forms.IntegerField()

How can user registration form for base django user model create ForeignKey user

I am new to django and am trying to set up a simple employee timesheet site that has multiple users. I set up two models one for the individual employee that has a ForeignKey of the base django user and a timesheet model that has a ForeignKey of the employee model. I'm not sure this is correct because when I use my registration form it just creates the base django user and not the "Employee" so when I want to create a new timesheet entry only the one employee is set up (set up with admin page). Can someone with more django experience tell me if there is a better way to do this (different model relationship, etc)
from django.urls import reverse
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
import datetime
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='employee')
payRate = models.DecimalField(max_digits=4, decimal_places=2, default=15.00, verbose_name=("Pay"))
vacTotal = models.DecimalField(max_digits=5, decimal_places=2, default=200.00, verbose_name=("Vacation"))
# META CLASS
class Meta:
verbose_name = 'employee'
verbose_name_plural = 'employees'
# TO STRING METHOD
def __str__(self):
return f"{self.user}"
class Tsheet(models.Model):
# CHOICES
WORK_CHOICES= (
('W', 'Regular Work'),
('V', 'Vacation'),
('S', 'Sick',),
('C','Call In'),
)
# DATABASE FIELDS
name = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='name')
workType = models.CharField(max_length=15,choices=WORK_CHOICES)
workDate = models.DateField(verbose_name=("Date"), default=datetime.date.today, editable=True)
workDescription = models.CharField(max_length=200)
workHours = models.DecimalField(max_digits=4, decimal_places=2, default=8.00, verbose_name=("Hours"))
workReviewed= models.BooleanField(default=False)
slug = models.SlugField(max_length=50, unique=True,
help_text='Unique value for timesheet entry URL, created automatically from name.')
# META CLASS
class Meta:
verbose_name = 'tsheet'
verbose_name_plural = 'tsheets'
# TO STRING METHOD
def __str__(self):
return f"{self.name} - {self.workDate} - {self.workHours} - {self.workType}"
# SAVE METHOD
# ABSOLUTE URL METHOD
def get_absolute_url(self):
return reverse('entry-detail', kwargs={'pk': self.pk})```
The right way to approach this is to extend the AbstractUser and add the fields there:
class User(AbstractUser):
payRate = models.DecimalField(max_digits=4, decimal_places=2, default=15.00, verbose_name=("Pay"))
vacTotal = models.DecimalField(max_digits=5, decimal_places=2, default=200.00, verbose_name=("Vacation"))
Then you have a single table with all the data from the default Django User as well as your specific fields

Django admin: list_display/search_fields foreign key of a foreign key in admin view

I have a question for django programmer that should be quite easy but at the moment I cannot figure out how to solve.
I have these three models (I simplify as much as I can):
class Nations(models.Model):
label = models.CharField(max_length=200)
iso2 = models.CharField(max_length=2, unique=True)
class Cities(models.Model):
label = models.CharField(max_length=200, null=True)
country_code = models.ForeignKey(Nations, to_field='iso2', on_delete=models.SET_NULL, null=True, verbose_name='Nation')
class Person(models.Model):
username = models.CharField(max_length=200, blank=False)
city = models.ForeignKey(Cities, on_delete=models.SET_NULL, null=True, blank=True)
As you can see, Person model is just connected with Cities model. What I need to do is to set PersonAdmin class in order to add in the admin view a column showing Nations.label value and make it searchable. In the example below I called this field city__country_code__label, just to make you figure out what I mean (but of course it doesn't work because there is no country_code object in Person model).
class PersonAdmin(admin.ModelAdmin):
list_display = ('id', 'username', 'city', 'city__country_code__label')
ordering = ('username',)
raw_id_fields = ('city',)
search_fields = ['username', 'city__label', 'city__country_code__label']
[...]
how can I ask Django to do this?
Thanx in advance!
Add a method to your model admin that takes a person obj and returns the country label. Then add the method to list_display.
class PersonAdmin(admin.ModelAdmin):
def country_label(self, obj):
return obj.city.country_code.label
list_display = ('id', 'username', 'city', 'country_label')
list_select_related = ['city__country_code']
ordering = ('username',)
raw_id_fields = ('city',)
search_fields = ['username', 'city__label', 'city__country_code__label']
See the list_display docs for more info.
Note I have used list_select_related to reduce the number of SQL queries.

Django User model fields at AdminModel

My purpose is to see at the admin site only user name, email and phone number.
I've create UserProfile by extending User model:
model.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
name = models.CharField(max_length=50, null=True,blank=True)
address = models.CharField(max_length=50, null=True,blank=True)
phone = models.CharField(max_length=20, null=True,blank=True)
country = models.CharField(max_length=20, null=True,blank=True)
state = models.CharField(max_length=20, null=True,blank=True)
zip = models.CharField(max_length=10, null=True,blank=True)
code = models.CharField(max_length=40, null=True)
def user_email(self):
return self.user.email
admin.py
from myApp.models import UserProfile
from django.contrib import admin
class UserProfileAdmin(admin.ModelAdmin):
fields = ('name','phone',)
list_display = ('name','user_email',)
admin.site.register(UserProfile, UserProfileAdmin)
so on the list_display it works, I can see only the columns I've chosen, but when I add 'user_email' ( fields = ('name','user_email', 'phone',) )to fields I get when I try to go to admin site:
'UserProfileAdmin.fields' refers to field 'user_email' that is missing from the form.
Fields on a related model use two underscores. Dunno if it'll work in the admin though.
list_display = ('name','user__email',)
Just because I recently used it and you maybe want this, too: If you wan't to add an inline admin to the "User" admin page in Django you can do this (at least in Django 1.3) by doing:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from models import UserProfile
class UserProfileInlineAdmin(admin.StackedInline):
model = UserProfile
class MyUserAdmin(UserAdmin):
inlines = [ UserProfileInlineAdmin ]
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
You can't put editable fields from a related model into an admin form, without using inlines. You can show the field as a readonly value: just add it to readonly_fields.