I am using django admin tabular-inline admin interface.
model.py
class Issues(TimeStampedModel):
issue_owner = models.ForeignKey(USER_MODEL, related_name='issue_owner')
issue_no = models.CharField(max_length = 500, null = True, blank = True)
class IssueComments(TimeStampedModel):
comment_owner = models.ForeignKey(USER_MODEL, related_name='comment_owner')
issue = models.ForeignKey(Issues, null=True, blank=True)
comment = models.TextField(null=True, blank=True)
I am trying to use tabular-inline in admin
admin.py
class IssueCommentsAdmin(admin.TabularInline):
model = IssueComments
extra = 1
def formfield_for_foreignkey(self, db_field, request=None,**kwargs):
field = super(IssueCommentsAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'comment_owner':
if request.user is not None:
field.queryset = field.queryset.filter(username = request.user.username)
if not field.queryset:
field.queryset = field.queryset.all()
else:
field.queryset = field.queryset.none()
return field
class IssuesAdmin(admin.ModelAdmin):
model = Issues
list_display = ('issue_no', 'title', 'owner_phone_number', 'status', 'issue_priority', 'classification')
inlines = [ IssueCommentsAdmin ]
def render_change_form(self, request, context, *args, **kwargs):
context['adminform'].form.fields['assigned_to_user'].queryset = User.objects.filter(is_staff=True)
return super(IssuesAdmin, self).render_change_form(request, context, args, kwargs)
i want to restrict the comment_owner to logged in user only in choice-field. I am able to do that also but issue i am facing here is for the comments whiche already have the comment_owner i want to keep that as it is.
Here comments which have comment_owner are not getting pre selected.
A little late but, for anyone
You should create another Model Admin class in admin.py and give permissions for this object. I recommend you change your classnames.
class IssueCommentsInline(admin.TabularInline):
...
class IssueCommentsAdmin(admin.ModelAdmin):
...
admin.site.register(IssueComments,IssueCommentsAdmin)
In this way, you can edit all data with get_queryset and field data with get_field_queryset.
regards!
Related
I'm reading a book about Django and I'm trying to reinterpret some content. (I'm using Django 2.1 and Python 3.6)
In the book different types of contents are associated with a module, like this:
class Module(models.Model):
course = models.ForeignKey(Course,
related_name='modules',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
order = OrderField(blank=True, for_fields=['course'])
class Meta:
ordering = ['order']
def __str__(self):
return '{}. {}'.format(self.order, self.title)
class Content(models.Model):
module = models.ForeignKey(Module,
related_name='contents',
on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE,
limit_choices_to={'model__in': (
'text',
'video',
'image',
'file')})
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
order = OrderField(blank=True, for_fields=['module'])
class Meta:
ordering = ['order']
class ItemBase(models.Model):
owner = models.ForeignKey(User,
related_name='%(class)s_related',
on_delete=models.CASCADE)
title = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def __str__(self):
return self.title
def render(self):
return render_to_string(
'courses/content/{}.html'.format(self._meta.model_name),
{'item': self})
class Text(ItemBase):
content = models.TextField()
class File(ItemBase):
file = models.FileField(upload_to='files')
class Image(ItemBase):
file = models.FileField(upload_to='images')
class Video(ItemBase):
url = models.URLField()
In the book there are CBVs to generate the right form for content types:
class ContentCreateUpdateView(TemplateResponseMixin, View):
module = None
model = None
obj = None
template_name = 'courses/manage/content/form.html'
def get_model(self, model_name):
if model_name in ['text', 'video', 'image', 'file']:
return apps.get_model(app_label='courses',
model_name=model_name)
return None
def get_form(self, model, *args, **kwargs):
Form = modelform_factory(model, exclude=['owner',
'order',
'created',
'updated'])
return Form(*args, **kwargs)
def dispatch(self, request, module_id, model_name, id=None):
self.module = get_object_or_404(Module,
id=module_id,
course__owner=request.user)
self.model = self.get_model(model_name)
if id:
self.obj = get_object_or_404(self.model,
id=id,
owner=request.user)
return super(ContentCreateUpdateView,
self).dispatch(request, module_id, model_name, id)
def get(self, request, module_id, model_name, id=None):
form = self.get_form(self.model, instance=self.obj)
return self.render_to_response({'form': form,
'object': self.obj})
def post(self, request, module_id, model_name, id=None):
form = self.get_form(self.model,
instance=self.obj,
data=request.POST,
files=request.FILES)
if form.is_valid():
obj = form.save(commit=False)
obj.owner = request.user
obj.save()
if not id:
# new content
Content.objects.create(module=self.module,
item=obj)
return redirect('module_content_list', self.module.id)
return self.render_to_response({'form': form,
'object': self.obj})
And contents are provided by users with special permissions.
Ok and now the question: I want the contents to be managed only by the admin, in the admin site.
I've tried this way:
class ContentInline(GenericStackedInline):
model = Content
extra = 0
#admin.register(Module)
class ModuleAdmin(admin.ModelAdmin):
list_display = ['title', 'order', 'course']
list_filter = ['course']
search_fields = ['title', 'description']
inlines = [ContentInline]
#admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
list_display = ['object_id', 'module', 'content_type', 'order']
but I have not been able to display in the admin site forms the field to upload the right content type.
Module page looks like this:
module page in admin site
While Content page is this:
content page in admin site
I've been searching for a solution for a while but I've not been able to find one. There is a similar topic here: link but suggested solutions imply javascript and/or additional packages while I'd like to do that only with Python and Django.
I've also read that a possible solution to display custom views in admin site is to write a view and then add it to the admin urls, then add it to the admin model.
Someone else suggested to use CBV from the book and use it in model admin.
I've tried to implement these suggestions with no luck.
In the last version of my code I try to use the CBV with as_view() this way (in views.py right after CBV):
content_create_update_view = ContentCreateUpdateView.as_view()
and then in admin.py:
#admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
list_display = ['object_id', 'module', 'content_type', 'order']
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('content/<int:object_id>/change/', self.admin_site.admin_view(content_create_update_view)),
]
return my_urls + urls
any help or suggestion is greatly appreciated.
In the end I was not able to find a solution to my problem, at least in the way I wanted to.
So I tried to get around the problem to get the same result in other ways. While looking for different methods, such as not using the GenericForeignKey or the ContentTypes I came across this link:avoid Django's GenericForeignKey
It seemed clear and simple, although maybe a little 'less elegant, so I implemented the following solution.
1) I modified the Content class by removing the GenericForeignKey and replacing it with a OneToOne relation for each type of supported content:
text = models.OneToOneField(Text, null=True, blank=True, on_delete=models.CASCADE)
file = models.OneToOneField(File, null=True, blank=True, on_delete=models.CASCADE)
image = models.OneToOneField(Image, null=True, blank=True, on_delete=models.CASCADE)
video = models.OneToOneField(Video, null=True, blank=True, on_delete=models.CASCADE)
and to make sure that only one attachment was matched at a time for each content, I added a check overwriting the save function:
def save(self, **kwargs):
assert [self.text, self.file, self.image, self.video].count(None) == 3
return super().save(**kwargs)
Finally I added a property to the class that would return the type of content:
#property
def target(self):
if self.text_id is not None:
return self.text
if self.file_id is not None:
return self.file
if self.image_id is not None:
return self.image
if self.video_id is not None:
return self.video
raise AssertionError("You have to set content!")
2) I modified the admin.py file by adding all the expected types of Content:
#admin.register(Text)
class TextAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
#admin.register(File)
class FileAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
#admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
#admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
Then I modified the ContentAdmin class by overriding the get_queryset function:
#admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(ContentAdmin, self).get_queryset(request)
qs = qs.select_related('text',
'file',
'image',
'video')
return qs
In this way, I am able to load the attachments in the admin interface and then combine them with the desired Content by selecting them conveniently from a list. Unfortunately this is a procedure in 2 steps, I would prefer to solve everything in one form admin side, but... it works.
#models.py
from django.db import models
from django.contrib.auth.models import User
CHOICES = (('1','Earned Leave'),('2','Casual Leave'),('3','Sick Leave'),('4','Paid Leave'))
class Leave(models.Model):
employee_ID = models.CharField(max_length = 20)
name = models.CharField(max_length = 50)
user = models.ForeignKey(User, on_delete = models.CASCADE, null =True)
department = models.CharField(max_length = 50)
designation = models.CharField(max_length = 50)
type_of_leave = models.CharField(max_length = 15, choices = CHOICES)
from_date = models.DateField(help_text = 'mm/dd/yy')
to_date = models.DateField(help_text = 'mm/dd/yy')
reporting_manager = models.CharField(max_length = 50, default = None, help_text = '0001_manager, 0002_manager')
reason = models.CharField(max_length= 180)
accepted = models.BooleanField(('accept'), default= False)
rejected = models.BooleanField(('reject'), default = False)
reason_reject = models.CharField(('reason for rejection'),max_length=50) // this didn't help me.
def __str__(self):
return self.name
This is a leave request form, where only of the two fields (accepted, rejected) should be selected and if the field rejected field is not selected then the reason_reject should not be shown in the /admin panel.
#forms.py
from django import forms
from lrequests import models
class LeaveRequestForm(forms.ModelForm):
class Meta:
fields = ("name", "employee_ID", "department", "designation", "type_of_leave", "from_date", "to_date", "reporting_manager", "reason")
model = models.Leave
The user fills the form and submits it. So, now the admin has to either accept it or reject it. The reason_reject field should appear to the admin only after once he selects rejected field. This all should happen only at the admin side.
#admin.py
from django.contrib import admin
from . import models
#admin.register(models.Leave)
class LeaveAdmin(admin.ModelAdmin):
list_display = ["name"]
#"employee_ID", "department", "designation", "type_of_leave", "from_date", "to_date", "reporting_manager", "reason", "accepted", "rejected", "reason_reject"
list_filter = ['department','type_of_leave']
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return (qs.filter(reporting_manager=request.user.username) or qs.filter(employee_ID=request.user.username))
# def get_readonly_fields(self, request, obj=None):
# # the logged in user can be accessed through the request object
# if obj and request.user.is_staff:
# readonly_fields = [f.name for f in self.opts.fields]
# readonly_fields.remove('accepted')
# readonly_fields.remove('rejected')
# return readonly_fields
#the above chunk of get_readonly_fields was written so that the admin could only either 'accept' or 'reject' the form.
def get_fields(self, request, obj=None):
fields = [f.name for f in self.opts.fields]
if obj and obj.rejected:
fields.append('reason_reject')
return fields
By running the above code I get
KeyError at /admin/lrequests/leave/15/change/ and
"Key 'id' not found in 'LeaveForm'. Choices are: accepted, department, designation, employee_ID, from_date, name, reason, reason_reject, rejected, reporting_manager, to_date, type_of_leave, user."
The entire premise is flawed. You cannot do this kind of logic in the model definition, because at that point there is no instance so no values to compare. And even if you could, it still wouldn't make sense because the model definition defines the database columns that the model has; you can't have different instances having different columns in the db. A model definition is for the entire model. You need to have reason_reject in the class definition.
What you might want to do is to change the form depending on the values in the model. You can do this in the admin by overriding the get_fields method.
class LeaveAdmin(admin.ModelAdmin):
...
def get_fields(self, request, obj=None):
fields = [....list of fields for the form...]
if obj and obj.rejected:
fields.append('reason_reject')
return fields
Note, you might want to think about whether you really need separate accepted/rejected fields; it would be better to have a single field for the state of the application: accepted or rejected. You can represent those either as individual radio buttons or as a dropdown.
STATUS_CHOICES = (
(1, 'Accepted'),
(0, 'Rejected'),
)
status = models.IntegerField(choices=STATUS_CHOICES, blank=True, null=True)
Firstly, define it as a drop down menu. That is with choices like
STATUS_CHOICES = (('0', 'Rejected'),('1', 'Accepted'),)
class Leave(models.Model):
...
status = models.CharField(max_length = 15, choices = STATUS_CHOICES)
and then in admin.py
class LeaveAdmin(admin.ModelAdmin):
...
radio_fields = {"status": admin.HORIZONTAL}
Consider an app where people can participate in a contest.
I have a Contest and ContestProblem Model ready. I want to have following features for the contest:
A contest can have many problems
A problem can not appear in more than one contest
In my models.py, I have:
class ProblemsInContest(CreateUpdateDateModel):
contest = models.ForeignKey(Contest)
problem = models.ForeignKey(ContestProblem)
class Meta:
verbose_name = "Problem in Contest"
verbose_name_plural = "Problems in Contest"
def __str__(self):
return "{problem}".format(problem=self.problem)
In my admin.py, I have:
class ContestProblemInline(admin.TabularInline):
model = ProblemsInContest
extra = 1
class ContestAdmin(admin.ModelAdmin):
inlines = [
ContestProblemInline,
]
This is how my Admin Form look:
I am using Django Admin to add problems to a contest. The problem being the fact that in the Problem dropdown, it shows me all the ContestProblem but I want to limit it to only those ContestProblem which does not appear in any other contest.
Any hints or advice or references to achieve the desired results will be highly appreciated.
class ContestProblemInline(admin.TabularInline):
model = ProblemsInContest
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
field = super(RoomInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'your_field_name':
if request._obj_ is not None:
field.queryset = field.queryset.filter(your_field_name = request._obj_)
else:
field.queryset = field.queryset.none()
return field
class ContestAdmin(admin.ModelAdmin):
inlines = (ContestProblemInline,)
def get_form(self, request, obj=None, **kwargs):
# just save obj reference for future processing in Inline
request._obj_ = obj
return super(ContestAdmin, self).get_form(request, obj, **kwargs)
I created a django form (IssueForm) which is meant to be used to register an object which is instance of one of my models (Issue). Following are the model:
model.py
class Issue(models.Model):
TYPE_FIELDS = [
("Math", "Math"),
("Physics", "Physics"),
("Programming", "Programming"),
("Arts", "Arts")
]
issue_text = models.TextField(default="Please insert text")
issue_description = models.TextField(default="Newly created")
issue_deadline = models.DateField()
issue_field = models.CharField(max_length=30, choices=TYPE_FIELDS)
published_by = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.issue_description
the form used:
forms.py
class IssueForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(IssueForm, self).__init__(*args, **kwargs)
TYPE_FIELDS = [
("Math", "Math"),
("Physics", "Physics"),
("Programming", "Programming"),
("Arts", "Arts")
]
issue_text = forms.CharField(widget=forms.Textarea, required=True)
issue_description = forms.CharField(widget=forms.Textarea, required=True)
issue_deadline = forms.DateField(required=True)
issue_fields = forms.ChoiceField(choices=TYPE_FIELDS, required=True)
class Meta:
model = Issue
fields = [
'issue_text',
'issue_description',
'issue_deadline',
'issue_fields'
]
def save(self, commit=True):
issue = super(IssueForm, self).save(commit=False)
issue.issue_text = self.cleaned_data['issue_text']
issue.issue_description = self.cleaned_data['issue_description']
issue.issue_deadline = self.cleaned_data['issue_deadline']
issue.issue_fields = self.cleaned_data['issue_fields']
if commit:
issue.published_by = self.user
issue.save()
return issue
and the related view:
views.py
def create_issue(request):
if ExtendedUser.objects.filter(user=request.user).exists():
if request.method == 'POST':
form = IssueForm(request.user, request.POST)
if form.is_valid():
form.save()
return redirect("/issues")
else:
form = IssueForm(request.user)
args = {'form': form}
return render(request, "issues/create_issue.html", args)
else:
raise Http404("You are not allowed to perform this action")
The forms works for every field in the model, they are all registered right, except for issue_fields. If i try giving a default value to the field in the model, that is the value that is saved on the database, otherwise I just get an empty field. Also I believe the problem comes from the form used, because if i try to create a new issue from the django admin interface it works just fine.
I feel like it's one of those silly mistakes, but I'm just starting with django and python in general and cannot figure it out on my own.
Thank you for your time!!
The field on your model is called issue_field, but you set issue_fields.
Note that also you are doing far more work here than necessary. Your save method completely duplicates what the superclass does already; you should remove all that code except for the setting of the user value.
enter code hereIf you want to use Choices, you haven't to write one more time list of choices in your forms.py file.
This is an example :
#In your models.py file
LIST_CHOICE = (('A','A'), ('B','B'))
class Test(models.Model) :
foo = models.CharField(choices=LIST_CHOICE, verbose_name="foo")
and
#In your form.py file
TestForm(forms.Modelform) :
class Meta :
model = Test
fields = ['foo']
It's not necessary to overwrite LIST_CHOICE in your form file ;)
So, dont touch to your model.py file, but in your form.py file, just write :
class IssueForm(forms.ModelForm):
issue_text = forms.CharField(widget=forms.Textarea)
issue_description = forms.CharField(widget=forms.Textarea)
def __init__(self, user, *args, **kwargs):
self.user = user
super(IssueForm, self).__init__(*args, **kwargs)
class Meta:
model = Issue
fields = [
'issue_text',
'issue_description',
'issue_deadline',
'issue_fields'
]
Don't forget to remove s in issue_field ;)
We're required to have two separate forms for two different types of users. Call them Client and Provider. Client would be the parent, base user, while Provider is a sort of extension. At any point a Client could become a Provider as well, while still maintaining status and information as a Client. So a Provider has both permissions as a Client and as a Provider.
I'm new to Django. All we're trying to do is register either user type, but have a one to one relation between Provider and Client tables if a user registers as a Provider straight away.
The issue we're having is in the adapter, we think. A provider registers fine, but ends up in the users_user table with no entry in the generated users_provider table. Is it the way we're trying to save and relate these two entities in the database, or something else?
We're trying to utilize allauth for authentication and registration.
Our code:
models.py:
class User(AbstractUser):
name = models.CharField(_('Name of User'), blank=True, max_length=255)
def __str__(self):
return self.username
def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username})
SEX = (
("M","MALE"),
("F","FEMALE"),
)
birthdate = models.DateField(_('Birth Date'), default=django.utils.timezone.now, blank=False)
sex = models.CharField(_('Sex'), choices=SEX, max_length=1, default="M")
isProvider = models.BooleanField(_('Provider'), default=False)
#Using User, not models.Model
class Provider(User):
HAS_BUSINESS = (
('YES','YES'),
('NO','NO'),
)
#Resolving asociation 1:1 to User
#NOTE: AUTH_USER_MODEL = users.User in setting
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
has_business = models.CharField(_('Do you have your own business?'),max_length=2, choices=HAS_BUSINESS, default='NO')
isProvider = True
our forms.py
class ProviderForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
Provider.isProvider = True
class Meta:
model = Provider
fields = '__all__'
exclude = GENERAL_EXCLUSIONS + [
'owner',
]
class ClientForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
class Meta:
model = User
fields = "__all__"
exclude = GENERAL_EXCLUSIONS
def is_active(self):
return False
def __init__(self, *args, **kwargs):
super(ClientForm, self).__init__(*args, **kwargs)
views.py:
class ProviderRegisterView(SignupView):
template_name = 'account/form_provider.html'
form_class = ProviderForm
redirect_field_name = 'next'
view_name = 'registerprovider'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ProviderRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerprovider = ProviderRegisterView.as_view()
#View para el formulario de registro de usuarios clientes
class ClientRegisterView(SignupView):
template_name = 'account/form_client.html'
form_class = ClientForm
redirect_field_name = 'next'
view_name = 'registerclient'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ClienteRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerclient = ClienteRegisterView.as_view()
finally, our adapter.py:
#Per allauth documentation, settings changed:
#ACCOUNT_ADAPTER = 'projectname.users.adapters.RegisterUserAdapter'
class RegisterUserAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
data = form.cleaned_data
user.first_name = data['name']
user.last_name = data['lastname']
#Saving Client info
user.sex = data['sex']
user.birthdate = data['birthdate']
#Normal allauth saves
user.username = data['username']
user.email = data['email']
if user.isProvider:
p = Provider()
p.owner = user
p.has_business = data['has_business']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
#Save user
user.save()
#If it's also a Provider, save the Provider
if user.isProvider:
p.save()
return user
Any help or tips would be greatly appreciated. If I left something out, please let me know. I'm not sure if the problem is in the model itself, the way we represent the form, or the adapter. The way it stands, it doesn't matter what form we use, it's always saved as the base User table (our Client) and the Provider table never gets information saved to it.
With Django's new custom user model, only one user model can be set as settings.AUTH_USER_MODEL. In your example, you can set this to your User model.
Then for the optional provider data, create a separate model that is referenced by OneToOneField from your User model.
class User(AbstractUser):
...
provider = models.OneToOneField(Provider, null=True)
class Provider(models.Model):
...
This is the easiest way to work with multiple user types in Django, given the AUTH_USER_MODEL constraint.
Also, it's best to only subclass abstract models, otherwise you get multitable inheritance which results in hidden implied JOINs, degrading performance.
Finally, you can create the Provider object in your custom form's form.is_valid() method and assign user.provider = provider.