Displaying fields not intended to be edited in ModelAdmin - django

I have a custom contact form for which I create a sent_time field using auto_now_add to save the time when the user had sent the message.
I am able to list all the information on the listing view of the admin panel however when I try to enter a specific message I hit the following error:
'sent_time' cannot be specified for GeneralContact model form as it is a non-editable field
My attempt to make the fields readonly in the ModelAdmin results in the same error
class GeneralContactAdmin(ModelAdmin):
"""
Admin model for general correspondence via
the main contact form on the information page
"""
model = GeneralContact
list_display = GeneralContact.__all__
search_fields = GeneralContact.__all__
readonly_fields = GeneralContact.__all__
ordering = ('-sent_time',)
list_filter = ('sent_time', 'has_response')
Surely it is possible to be displayed only, perhaps I've done something incorrectly in my models?
Here is the base model I use for the contact model
class ContactFormBase(models.Model):
__all__ = (
'sent_time', 'sender_name', 'sender_email',
'sender_message', 'has_response', 'responded_on'
)
sent_time = models.DateTimeField(auto_now_add=True)
sender_name = models.CharField()
sender_email = models.EmailField()
sender_message = models.TextField()
has_response = models.BooleanField(
default=False,
help_text='Select whether this message has been replied to by an admin.',
)
responded_on = models.DateTimeField(blank=True, null=True)
panels = [
FieldRowPanel([
FieldPanel('sender_name'),
FieldPanel('sender_email'),
]),
FieldPanel('sent_time'),
FieldPanel('sender_message'),
FieldRowPanel([
FieldPanel('has_response'),
FieldPanel('responded_on'),
])
]
class Meta:
abstract = True
ordering = ['-sent_time',]
The actual class being used is rather plain, perhaps something needs to be done here to allow display of readonly fields?
class GeneralContact(ContactFormBase, models.Model):
panels = ContactFormBase.panels
class Meta:
verbose_name = 'General Contact Entry'
verbose_name_plural = 'General Contact Entries'
In the list view all the information is able to be displayed. In the editing view, ideally there would be all of the information about the message and sender as readonly fields and an option for the admin to change the has_response value based on whether someone has responded or not.
In what way could I achieve this?
update
After seeing this Q&A I have changed the auto_now_add to use django.utils.timezone.now as the default on the sent_time attribute and life seems better, the error from the start of the question is gone and the edit view loads up entirely. However, now all the fields are editable which is not desirable.
Looking into the ModelAdmin class provided by Wagtail it appears that readonly_fields isn't available and perhaps only a feature of the django admin class of the same name. So I'm not sure what to do here. Wagtails HelpPanel type of output is what I'm looking for, and I had an idea to use that to display the data but I'm not sure what that looks like or even how it'd be done as I'm just learning django and wagtail.
update 2
Attempted to use HelpPanel instead of FieldPanel in order to try display the values but seems as if the HelpPanel doesn't retrieve the value of the attributes. Checking through these docs I see the mention of things like djangos readonly_field is not included which confirms why one of my former attempts didn't work but I did find mention of inspect_view_enabled which displays the values in a read only fashion and after trying it out it looks very much how I was trying to get it, alas, nothing there is editable which makes sense but I am getting closer.
I am wondering if a good solution would be to override the view or template used for GeneralContactAdmin but unsure if that's the right way to go about it just to output some text for one class.
A simpler solution is to keep the inspect view and only add the has_response to the edit view, but two views, one of which would only be a checkbox is not a nice for UX.
Surely there is a better way to solve this?

Related

filter ChoiceField dynamically based on another field

SOLVED
thanks a lot to Abdul Aziz Barkat who in his comment posted a link to a perfect tutorial.
basically I created a view that gives me the small part of html that corresponds to the list of filtered data and I used ajax calls to reload only that portion of the template. below I leave the link to the guide for more explanations:
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html.
I have a Model Form that must be filled in by the user.
in the form there are 3 drop-down menus (foreignkey in the model) which depend on each other. that is, when the user selects a value in the first field the results in the second must dynamically depend on that.
i know i have to use ajax but i never used it and i don't know where to start
I know that to filter I have to set the queryset e.g. form.fields['stabilimento'].queryset = Stabilimento.objects.filter(), but I don't know how to do it using ajax
model.py
class Ticket(models.Model):
stabilimento = models.ForeignKey(Stabilimento, on_delete = models.RESTRICT)
linea = models.ForeignKey(Linea, on_delete = models.RESTRICT)
macchina = models.ForeignKey(Macchina, on_delete = models.RESTRICT)
form.py
class NewTicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = '__all__'
thanks a lot to Abdul Aziz Barkat who in his comment posted a link to a perfect tutorial.
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html

How to display manytomany field in django admin with a related model in a user-friendly way?

I'm struggling to display a manytomany field in the admin with the related model in a user-friendly manner. The project is already up and running so adding a through table is not preferred.
The set-up is something along these lines;
class User(AbstractUser):
is_member_of_organization = models.ManyToManyField(Organization, blank=True, verbose_name=_("is member of organization(s)"), related_name='orgmembers')
class Organization(models.Model):
name = models.CharField(max_length=60, verbose_name=_("organization name"))
the only reasonable way I manage to display the related users with organization admin is via a TabularInline
admin.py
class UserOrgAdminInLine(admin.TabularInline):
model = User.is_admin_for_organization.through
extra = 0
#admin.register(Organization)
class OrganizationAdmin(admin.ModelAdmin):
inlines = (UserOrgAdminInLine,)
However, looking up users is not convenient as soon as their number increases. I would like something similar to filer_horizontal but I am not sure how to include it directly in the OrganizationAdmin admin class. Furthermore, I am using fieldsets (which I believe should have no special rules or syntax to it compared to ordinary fields = .
One little subquestion - in the tabular inline, when I use only model = User django throws an error that there is no foreign key to it, but when I use the additional .is_admin_for_organization.through it works, but there is no through table and I though that this work just in that case. Why is that?
Any help would be much appreciated.
Try adding
class UserOrgAdminInLine
raw_id_fields = ("is_member_of_organization",)

Django 1.8: How can I ensure that of Two Fields in a Model, At Least One or Only One must meet a condition?

For context, here is a menu system.
class Menu(models.Model):
...
class Link(models.Model):
...
class MenuItem(models.Model):
menu = models.ForeignKey(Menu)
submenu = models.ForeignKey(Menu, related_name='submenu', blank=True, null=True)
link = models.ForeignKey(Link, blank=True, null=True)
position = models.IntegerField()
I have two results I'm looking to achieve:
At least one of Submenu and Link must not be Null (submenu titles can have a link)
Only one of Submenu and Link must be null (submenu titles cannot have a link)
Any advanced validation is new to me, so a code example would be very helpful.
In this example, data will only be added via Django Admin
The documentation around model validation is poor. There are numerous (closed) issues referring to it, but it's still unclear.
This solution works, without making changes to any Forms:
from django.core.exceptions import ValidationError
class MenuItem(models.Model):
...
def clean(self):
super(MenuItem, self).clean()
if self.submenu is None and self.link is None:
raise ValidationError('Validation error text')
clean() has some default validation functionality, so the clean belonging to Model needs be called first.
The above ensures that at least one of the two fields are used, and raises the exception if not. I have only tested this in the Admin interface.
I don't know if this is the correct way to do this, and would love to know more if someone has a better understanding of model validation in Django. Coming from another languages and frameworks, this does feel like the natural way to write custom validation.

How to reference model joined across foreign key in django admin

I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.

Is there an option to prevent some values from being displayed in django autocomplete widget?

I am using django-autocomplete in my "edit user details view" for adding "friends"
django-autocomplete works perfect but displays also the "current user" (who is editing his profile) and the user "anonymous"
I want to exclude those two.
How can I accomplish that?
models.py
class Profile(UserenaLanguageBaseProfile):
friends = models.ManyToManyField(User,related_name='userfriends', blank=True, null=True)
forms.py
class EditProfileForm(forms.ModelForm):
class Meta:
widgets = {
'friends': MultipleAutocompleteWidget(Profile.friends),
}
It's not a complete answer, but you should do something along those lines:
autocomplete = AutocompleteView()
class ProfileAutocomplete(AutocompleteSettings):
queryset = Profile.objects.exclude(friends='anonymous')
autocomplete.register(Profile.friends, UserAutocomplete)
But this will not exclude the current user. To get that you will have override/extend the view method of your ProfileAutocomplete class. In this method you will need to get the user ID (probably from the session) and then exclude it from the queryset. If using the session is not working (it's possible, I didn't put too much time on it), you 'll probably have to modify the jquery_autocomplete.js script to pass the user to the view method.