Django Admin Fk grouping - python

How do I go about grouping a set of fields within the Django Admin. By this I mean
Table 1: User - User Key - User Name
Table 2: Post - Post Key - User Key (FK)
In the PostAdmin I would like to display the User Name of the Author also perform an action on the posts. This works but displays the user name for each post created by the user.
Is there a way I could just display the user name once but in the action update all posts created by the user? - Update all posts with is_sealed = true.
Model
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_("email address"), unique=True)
first_name = models.CharField(
_("first name"), max_length=50,
validators=[RegexValidator(
regex_alpha, code='invalid_first_name',
message=regex_alpha_message,
)]
)
middle_name = models.CharField(
_("middle name"), max_length=50, blank=True,
null=True, default=None,
validators=[RegexValidator(
regex_alpha, code='invalid_middle_name',
message=regex_alpha_message,
)]
)
class Post(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(_('title'), max_length=250)
label = models.ForeignKey(Label, verbose_name=_('label'))
significance = models.CharField(max_length=3, choices=SIGNIFICANCE_CHOICES)
is_sealed = models.BooleanField(_('Is Sealed?'), default=False)
event_date = models.DateField()
message = models.TextField(_("Message"), blank=True, default='')
PostAdmin (I need to fix the issue here)
class PostAdmin(admin.ModelAdmin):
list_display = ['owner','is_sealed']
ordering = ['owner']
actions = [open_vault]
search_fields = ['owner__email',]
list_filter = ['owner__email']

So, according to your comments, you must write a custom method inside UserAdmin for displaying posts related to a user/author, like this:
from django.utils.html import mark_safe
class UserAdmin(admin.ModelAdmin):
list_display = ['email', 'get_posts']
# ... other settings here
def get_posts(self, obj):
to_return = '<ul>'
for post in obj.post_set.all():
to_return += '<li>{}</li>'.format(post.title)
to_return += '</ul>'
return mark_safe(to_return)

Related

How to filter/hide fields in django rest framework HTML form using authenticated user

I created two models:
parcel (package) model,
'shelf' model to which parcels can be assigned.
class Parcel(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
name = models.CharField(max_length=100, blank=False)
contents = models.TextField(blank=False, validators=[MaxLengthValidator(1500)])
size = models.CharField(max_length=50, blank=True, validators=[package_size_validator])
weight = models.PositiveIntegerField(blank=True, null=True)
contact = models.CharField(max_length=50, blank=True)
creation_date = models.DateTimeField(auto_now_add=True)
last_modification = models.DateTimeField(auto_now=True)
code = models.CharField(max_length=30, unique=True, blank=False)
def __str__(self):
return f'Parcel: {self.code}, {self.owner}, {self.name}'
class ParcelShelf(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
name = models.CharField(max_length=100, blank=False)
creation_date = models.DateTimeField(auto_now_add=True)
last_modification = models.DateTimeField(auto_now=True)
parcels = models.ManyToManyField('Parcel', blank=True, related_name='shelf_parcel')
def __str__(self):
return f'ParcelShelf: {self.owner}, {self.name}'
I came to a solution where the logged-in user can see only his packages and shelves. The problem I have is with the many-to-many relationship where parcels can be added to shelves. I want to come to a solution where the logged in user can add to the shelf only those parcels which he is the owner, creator. It will look better in pictures.
All packages created by user t2#t2.com (user id = 17):
parcels list
Now the view when the user t2#t2.com wants to create a shelf:
shelf list
All packages are visible, while only those created by the user t2#t2.com should be available.
Code to serializer:
class ParcelShelfSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
parcels = serializers.HyperlinkedRelatedField(many=True, read_only=False, view_name='parcels_detail_view',
# queryset=Parcel.objects.filter(owner=17)
queryset=Parcel.objects.all()
)
class Meta:
model = ParcelShelf
fields = ('id', 'owner', 'name', 'creation_date', 'last_modification', 'parcels')
Below is a picture where only packages for a given, logged-in user are available:
shelf list
Code to serializer:
class ParcelShelfSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
parcels = serializers.HyperlinkedRelatedField(many=True, read_only=False, view_name='parcels_detail_view',
queryset=Parcel.objects.filter(owner=17)
# queryset=Parcel.objects.all()
)
class Meta:
model = ParcelShelf
fields = ('id', 'owner', 'name', 'creation_date', 'last_modification', 'parcels')
I got to the point where the 'solution' is in the 'queryset' argument.
All users: queryset=Parcel.objects.all()
Logged in user: queryset=Parcel.objects.filter(owner=17)
The problem is, this is hardcoded, and it should be something like: (owner=request.user). Unfortunately, I don't know how to achieve this in the serializer. I looked through other similar topics, but I didn't find a solution how to use the request method in the serializer field.
In addition, code in views:
class ParcelsShelfList(generics.ListCreateAPIView):
# queryset = ParcelShelf.objects.all()
serializer_class = ParcelShelfSerializer
def get_queryset(self):
user = self.request.user
if bool(user and user.is_staff and user.is_admin):
return ParcelShelf.objects.all()
else:
return ParcelShelf.objects.filter(owner=user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
UPDATE
Thanks to the help of #Amrez, who gave me a link to a similar topic, i was able to do it.
mentioned link: How can I filter DRF serializer HyperlinkedRelationField queryset based on request data?
I add this to my code in serializers.py:
def hyperlinked_related_field_by_owner(model, view_name, owner):
return serializers.HyperlinkedRelatedField(
many=True,
view_name=view_name,
queryset=model.objects.filter(owner=owner)
)
class ParcelShelfSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
parcels = serializers.HyperlinkedRelatedField(many=True,
read_only=False,
view_name='parcels_detail_view',
# queryset=Parcel.objects.filter(owner=17)
queryset=Parcel.objects.all()
)
def get_fields(self):
fields = super().get_fields()
owner = self.context['request'].user
fields['parcels'] = hyperlinked_related_field_by_owner(Parcel, 'parcels_detail_view', owner)
return fields
class Meta:
model = ParcelShelf
fields = ('id', 'owner', 'name', 'creation_date', 'last_modification', 'parcels')

How do I make a field optional to enter, in a django ModelForm?

Scenario:
I instantiate a ModelForm and pass it to a template which displays the form. When POST is submitted, code tries to search the database by any of the given inputs. I dont require all inputs to be entered as in the Model. I just need one (or more, if user desires to do an AND search) to be entered.
Question: How can I make any of the ModelForm fields optional, where in the Model, the field isnt optional. The field isnt optional in the Model because I have another ModelForm based on the same Model, where user is required to enter all his details.
My model:
class customer(models.Model):
# Need autoincrement, unique and primary
cstid = models.AutoField(primary_key=True, unique=True)
name = models.CharField(max_length=35)
age=models.IntegerField()
gender_choices = (('male', 'Male'),
('female', 'Female'))
gender = models.CharField(
choices=gender_choices, max_length=10, default='male')
maritalstatus_choices = ( ('married', 'Married'),
('unmarried', 'Unmarried'))
maritalstatus = models.CharField(
choices=maritalstatus_choices, max_length=10, default='Unmarried')
mobile = models.CharField(max_length=15, default='')
alternate = models.CharField(max_length=15, default='')
email = models.CharField(max_length=50, default='', blank=True)
address = models.CharField(max_length=80, default='', blank=True)
city = models.CharField(max_length=25, default='', blank=True)
occupation = models.CharField(max_length=25, default='', blank=True)
bloodgroup_choices = (('apos', 'A+'),
('aneg', 'A-'),
('bpos', 'B+'),
('bneg', 'B-'),
('opos', 'O+'),
('oneg', 'O-'),
('abpos', 'AB+'),
('abneg', 'AB-'),
('unspecified', '-')
)
bloodgroup = models.CharField(choices=bloodgroup_choices, max_length=3, default='-', blank=True)
class Meta:
unique_together = ["name", "mobile", "age"]
def __str__(self):
return self.name
My form:
class CheckinPatientMetaForm(ModelForm):
class Meta:
model = customer
exclude = [
'gender',
'maritalstatus',
'occupation',
'bloodgroup'
]
views.py:
def checkin_patient(request):
results = ''
if request.method == 'POST':
form = CheckinPatientMetaForm(request.POST)
print("POST data",request.POST)
else:
form = CheckinPatientMetaForm()
return render(request, 'clinic/checkin.html', {'rnd_num': randomnumber(), 'form': form, 'SearchResults': results})
As #bdbd mentioned in comments, you can do it by specifying by required=False.
For example, if you want to age field to be optional, add it explicitly as
from django import forms
class CheckinPatientMetaForm(ModelForm):
age = forms.IntegerField(required=False)
class Meta:
model = customer
exclude = [
'gender',
'maritalstatus',
'occupation',
'bloodgroup'
]

How to display multiple objects from fields in Django Admin

I am a bit stumped as to how I can add multiple access_token and items_ids in Django Admin. The models and apps involved are as follows. This is my first post so please forgive if it isn't in proper format.
Trans/models.py
class Exchange(models.Model):
created = models.DateTimeField()
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='token', on_delete=models.CASCADE)
access_token = models.CharField(max_length=300, blank=True, default='')
item_id = models.CharField(max_length=300, blank=True, default='')
request_id = models.CharField(max_length=300, blank=True, default='')
class Meta:
ordering = ('item_id',)
I have setup a userprofile section for the admin:
Users/models.py
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True, verbose_name='user', related_name='profile', on_delete=models.CASCADE)
avatar_url = models.CharField(max_length=256, blank=True, null=True)
dob = models.DateField(verbose_name="dob", blank=True, null=True)
public_token = models.CharField(max_length=100, blank=True, null=True, verbose_name='public_token')
access_token = models.CharField(max_length=100, blank=True, null=True, verbose_name='access_token')
item_id = models.CharField(max_length=100, blank=True, null=True, verbose_name='item_ID')
just_signed_up = models.BooleanField(default=True)
def __str__(self):
return force_text(self.user)
class Meta():
db_table = 'user_profile'
users/forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('user', 'public_token', 'access_token', 'item_id',)
users/admin.py
class UserProfileAdmin(admin.ModelAdmin):
search_fields = ('user', 'dob', 'public_token', 'access_token', 'item_id',)
ordering = ('user',)
list_select_related = ('user',)
admin.site.register(UserProfile, UserProfileAdmin)
class UserProfileAdminInline(admin.TabularInline):
model = UserProfile
I'm really just stumped as I tried making many to many field but couldnt seem to link correctly and or the process broke when testing in a sandbox environment. Any help would be greatly appreciated! In my case I need to record multiple access_tokens and item_ids for each user.
It's a little bit confusing what you are asking...particularly the way that your data model is setup....but I'm going to make a couple of assumptions in my answer (it would be helpful to better understand what you are trying to do at a high level).
I think what you are wanting to do is to be able to configure multiple Exchange objects per user profile...in which case I would set things up this way:
1. The related_name field on the FK to the user profile in the exchange model will be how you access multiple exchanges...so in this case you probably want a pluralized name.
2. To be able to edit multiple in the Django Admin you will need to setup an InlineAdmin object.
3. The CharFields that are actually ON the UserProfile will only ever be single fields...if you want multiple then you need to move them to another related object (like the Exchange model).
4. I don't think what you want here is a ManyToMany as that would imply user's would be sharing these tokens and item ids (or Exchanges?)...but maybe that is what you want...in which case you should change the ForeignKey to UserProfile from the Exchange model to a ManyToManyField. The rest of this post assumes you don't want that.
trans/models.py
from django.db import models
from django.conf import settings
class Exchange(models.Model):
class Meta:
ordering = ('item_id', )
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='exchanges', on_delete=models.CASCADE)
access_token = models.CharField(max_length=300, blank=True)
item_id = models.CharField(max_length=300, blank=True)
request_id = models.CharField(max_length=300, blank=True)
users/models.py
from django.db import models
from django.conf import settings
class UserProfile(models.Model):
class Meta:
db_table = 'user_profile'
user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True, verbose_name='user', related_name='profile', on_delete=models.CASCADE)
avatar_url = models.CharField(max_length=256, blank=True)
dob = models.DateField(verbose_name="dob", blank=True, null=True)
public_token = models.CharField(max_length=100, blank=True, null=True)
access_token = models.CharField(max_length=100, blank=True, null=True)
item_id = models.CharField(max_length=100, blank=True, null=True)
just_signed_up = models.BooleanField(default=True)
def __str__(self):
return force_text(self.user)
users/admin.py
from django.contrib import admin
from trans.models import Exchange
from users.models import UserProfile
class ExchangeAdminInline(admin.TabularInline):
model = Exchange
class UserProfileAdmin(admin.ModelAdmin):
inlines = (ExchangeAdminInline, )
search_fields = ('user', 'dob', 'public_token', 'access_token', 'item_id', )
ordering = ('user', )
list_select_related = ('user', )
admin.site.register(UserProfile, UserProfileAdmin)
There is a lot that you can do to configure the inlines to behave how you want...but that's the basics.

Display a model field as readonly in Django admin

I want to display a model field ie module_name as read only in django user profile edit page. The model Category has a manytomany relationship with Profile model.
Category
id category_name module_name
1 A X
2 B Y
profile_category
id profile_id category_id
1 2 1
So for eg. when I am on the admin page to editing a user id of 2 then I would like to display the module name X if category id 1 is assigned to the user id 2
models.py
class Category(models.Model):
category_name = models.CharField(max_length=150, blank=False, null=True, default="", unique=True)
module_name = models.TextField(blank=False, null=True)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name ='profile')
phone_number = PhoneNumberField( blank=True, null=True)
organisation = models.CharField(max_length=30, blank=True)
category = models.ManyToManyField(Category)
admin.py
class ProfileInline(admin.StackedInline):
model = Profile
filter_horizontal = ('category',)
class CustomUserAdmin(UserAdmin):
inlines = (ProfileInline, )
list_select_related = ( 'profile', )
Please suggest if this is possible or not.
Any help/suggestion is highly appreciated. Thanks in advance.
Method 1
Use readonly_fields which is inherited from admin.BaseModelAdmin.
class ProfileInline(admin.StackedInline):
model = Profile
readonly_fields = ('module_names',)
def module_names(self, instance):
# Write a get-method for a list of module names in the class Profile
# return HTML string which will be display in the form
return format_html_join(
mark_safe('<br/>'),
'{}',
((line,) for line in instance.get_module_names()),
) or mark_safe("<span>The user belongs to no category</span>")
# short_description functions like a model field's verbose_name
module_names.short_description = "Module names"
Method 2
For older versions of admin site, you can hook the get_fieldsets method of admin.StackedInline. This method was posted here with a nice example.

Django: restricting possible dates

I am new to Django. I am using Django 1.8 and Python 3.4.
class Card(models.Model):
STATUS_EXPIRED = "EX"
STATUS_ACTIVE = "AC"
STATUS_DEACTIVATED = "DE"
STATUS_CHOICES = (
(STATUS_ACTIVE, "Active"),
(STATUS_EXPIRED, "Expired"),
(STATUS_DEACTIVATED, "Deactivated")
)
id = models.AutoField(primary_key = True)
series = models.CharField(verbose_name="Series", max_length=8, null=False, blank=False)
number = models.CharField(verbose_name="Number", max_length=16, null=False, blank=False)
issue_date = models.DateTimeField(verbose_name="Issue Date", auto_now=True, null=False, blank=False)
expire_date = models.DateTimeField(verbose_name="Expiry Date", auto_now=False, null=False, blank=False)
status = models.CharField(verbose_name="Status", max_length=3, null=False, blank=False, default="AC")
How do I ensure that expire_date is never less than issue_date in the database? How do I enforce this condition in Django-admin interface when creating objects of Card class?
You can do a form validation in admin like this;
from models import Card
from django.contrib import admin
from django import forms
class CardForm(forms.ModelForm):
class Meta:
model = Card
fields = ('series', 'number', 'issue_date', 'expire_date', 'status')
def clean(self):
issue_date = self.cleaned_data.get('issue_date')
expire_date = self.cleaned_data.get('expire_date')
if expire_date < issue_date:
raise forms.ValidationError("Wrong dates")
return self.cleaned_data
class CardAdmin(admin.ModelAdmin):
form = CardForm
list_display = ('series', 'number', 'issue_date', 'expire_date', 'status')
admin.site.register(Card, CardAdmin)
You can use a custom model form ( Model forms ) and validate the data before saving and while registering do something like
class TestAdmin(admin.ModelAdmin):
form = ModelForm
list_display = ('field1', 'field2')
admin.site.register(Lecture, LectureAdmin)

Categories