Referencing a Django contrib.admin user via a foreign key? - django

I'm developing a small Django site and I'm using django.contrib.admin to handle content management. I'd like to capture the first name & last name of the author (an Admin user) of an Article on its initial save (and not update it if another user edits the Article).
ie.
class Article(models.Model)
title = models.CharField(max_length=50)
pub_date = models.DateTimeField('date published')
author = ForeignKey(???)
...
What do I need to write to grab this user's first name & last name fields when creating a new Article object? I'd default to their admin username if those fields are blank.

Have your model use the User object:
author = models.ForeignKey(User)
To prevent this field from being changeable on update, check out this other SO post:
Django admin: exclude field on change form only
To change the admin's Select field to use first/last name, you could try this snippet:
http://djangosnippets.org/snippets/1642/
To change the admin's view, assuming you are using the built-in templates, you could add a custom column as described on this post: How do I add a custom column with a hyperlink in the django admin interface?
class AuthorAdmin(admin.ModelAdmin):
list_display = ('author_name',)
def my_author_name(self, obj):
if obj.author.first_name and obj.author.last_name:
return '%s %s' % (obj.author.first_name, obj.author.last_name)
else:
return obj.author.username
my_author_name.allow_tags = True
my_author_name.short_description = 'Author'

I think you are looking for this:
author = models.ForeignKey(User)

It looks like the best way to handle a None or blank result from get_full_name is to just populate User.author with models.ForeignKey(User) and then — at the template level — use the following:
{{ user.get_full_name|default:user.username }}
... via this SO answer. This allows me to perform queries on a User's Articles, but still gracefully handles blank first_name & last_name fields if a User hasn't entered them yet, but will also update dynamically when they have).

Related

Force model fields in Django

When creating a model in Django like this example:
class Musician(models.Model):
first_name = models.CharField(max_length=50, primary_key=True)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
I noticed some problem (not sure if that's best word) with this approach. There is nothing preventing you from creating something like:
musician = Musician()
musician.save()
effectively having primary_key value equal to None. I would like to force user to set first_name, but frankly speaking I cannot find any simple solution for that.
Is there a way to achieve this?
First of all, don't set first_name as primary key. Just leave the default primary key as the id field. A primary key needs to be unique (a first_name isn't) and should not be something a user enters.
Second, it's true that you cannot enforce a CharField to not be empty at the database level. But you can enforce it at the code level, so that anytime you create a Django Form and validate it, it will raise an error.
In fact, Django does it automatically for you, in your case. By default first_name is a required field, since you didn't set blank=True.
So if you do:
musician = Musician()
musician.full_clean()
this raises a ValidationError.
If you create a form for your model (which is what you need if you want users to create a Musician):
class MusicianForm(ModelForm):
class Meta:
model = Musician
fields = '__all__'
form = MusicianForm(data={})
form.instance.first_name
# ''
form.is_valid()
# False
form.save()
# ValueError: The Musician could not be created because the data didn't validate.
You'll also see that if you register Musician in admin.py for django admin site, you can't leave any of the fields empty. It just won't save.

Create forms from model, save to another

I'm building a survey-type app, where the questions are entered/stored in a model (by an admin). I want to use this model to auto-create a user facging form (the response will be stored in another model.
class Question(models.Model):
question_text = models.CharField(max_length=150)
questionnaire = models.ForeignKey(Questionnaire) # 'lump' questions together
class Response(models.Model):
question = models.ForeignKey(Question)
response_text = models.CharField(max_length=150)
user = models.IntegerField() # used to record user ID
I'm not sure what's the correct approach - using a ModelForm (populating through init) or a 'regular' form - where I'm not sure how I output my results - using print or adding to a form property.
Thanks.
In your init you can
self.fields['your_field']=forms.ModelChoiceField(queryset=YourQuerySet.objects.get(pk=X)) // alternatively a filter with first()
to enforce the initial foreign key object

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

How can I dynamically change list of fields displayed in admin interface depending on the choice in the first field?

I want the admin interface to show disctrict field only if I choose 'B' as the category. If I choose 'W' I want all fields of Offer model to be displayed. Is it possible to show selected (filtered) fields in admin page depending on the choice in other field in the same model? Thanks in advance for your help.
My models:
class Category(models.Model):
NAME_CHOICES = (
('B', 'BLACK'),
('W', 'WHITE'),
)
name = models.CharField(max_length=200, choices=NAME_CHOICES)
class Meta:
verbose_name_plural = 'Categories'
def __unicode__(self):
return self.get_name_display()
class Offer(models.Model):
category = models.ForeignKey(Category, verbose_name='Kategoria')
city = models.CharField(max_length=128, verbose_name='Miasto')
province = models.CharField(max_length=3)
district = models.CharField(max_length=128, verbose_name='Dzielnica')
def __unicode__(self):
return "Offer number %s" % (self.id)
First of all I must to tell, that django works only in sync way. So if you want to choose which input to use, you must send a request and wait a feedback. In my opinion there're no straight way to do this task correctly.
And I see a few solutions:
1) You can use jQuery for that. But the main problem is that django has a own admin system with a built-in widgets. You can try to customize it in two ways:
Take an app with this option (for example, django-admin-tools) and create custom behavior on your form;
manage.py collectstatic and after that going to admin folder and create custom jQuery script.
2) Build a custom admin form for your model with ModelChoiceField. I don't quit sure about this field behavior really help you, but you can investigate that.
If I need to do this task, I choose first way with admin static and custom jQuery.

How to display a value where there is none in Django admin?

Is there a Djangotastic way to display a default value for a field in the admin when there isn't a value? Like 'n/a', but not to save that to the database?
When I set all the fields in the model below to readonly in the admin, the front-end display looks like the image at the bottom. It feels visually collapsed like it should have a value or a box or something. If there isn't an easy way to do what I am looking for, then is there another solution to make the front-end admin more clear for the user?
class Package(models.Model):
packaging_format = models.CharField(max_length=40)
package_delivery_pattern = models.CharField(max_length=30, blank=True)
package_delivery_comments = models.CharField(max_length=250, blank=True)
package_manifest_filename = models.CharField(max_length=50)
package_description = models.CharField(max_length=250, blank=True)
package_naming_pattern = models.CharField(max_length=50)
Screenshot of fields as displayed in the admin:
What's happening is that your actually saving a empty string '' in your CharFields instead of None values (because of the blank=True). So the Django-admin is showing the string you saved in the db (in this case, nothing).
If you change your CharFields to null=True instead of blank=True, you will be saving NULL in your database instead of an empty string. And that way, you will get the behaviour you want.
EDIT: I know this solution is not recommended (following Django Docs), but that's the behaviour you wanted. Django-admin is just showing you the string you have in the database, which is ''.
Another solution that comes to my mind is to modify the ModelAdmin for your Package model, something like:
class PackageAdmin(admin.ModelAdmin):
readonly_fields = ['show_package_delivery_pattern', ...]
def show_package_delivery_pattern(self, obj):
if obj.package_delivery_pattern:
return obj.package_delivery_pattern
else:
return 'N/A'
# same with all your CharFields..
As of Django 1.9 you can use empty_value_display at the site, model, or field level in the Django admin. At the model level:
class YourModelAdmin(admin.ModelAdmin):
empty_value_display = '---'