Django admin not showing choices for Foreign key field - python

I am creating a small chat app and I have problems making a message in the admin because of the drop down for the Messagemie model for field chat. Notice in the picture below it does not show the required values associated with the Conversation model. The values that the conversation field in the Conversation model accepts are of the form "number-number", e.g. 5-10, 11-21 etc. Note that I have created a mechanism not shown below which converts such input formats to strings for non Django admin input (When users start a new conversation).
The conversation field is of type CharField. I suspect the reason why Django admin form does not show the required values is because of the field type, however I am not sure. Also it could be that because Django admin is not converting the input to string thus showing just Conversation object in the drop down. Why is Django admin not showing the correct values for the chat input field?
#python_2_unicode_compatible
class Conversation(models.Model):
conversation = models.CharField(unique=True, max_length=150)
email_1 = models.ForeignKey(Usermie, to_field="email", related_name="email_1_convo")
email_2 = models.ForeignKey(Usermie, to_field="email", related_name="email_2_convo")
#python_2_unicode_compatible
class Messagemie(models.Model):
sender = models.ForeignKey(Usermie, to_field="email", related_name="email_sender")
receiver = models.ForeignKey(Usermie, to_field="email", related_name="email_receiver")
# The username is the sender's username
sender_username = models.CharField( max_length=50)
receiver_username = models.CharField(max_length=50)
message = models.TextField()
chat = models.ForeignKey(Conversation, to_field="conversation", related_name="conversation_chat")
Picture showing Messagemie model chatfield selection in admin
Picture of input values in Conversation model Django admin.

Django admin shows the string representation of the object in the dropdown. This could be obtained by calling str(object). You can modify this behaviour by overriding the __str__ method in your class.
The implementation of the Django base model class (django.db.models.Model) has an implementation like below (for python3) -
def __str__(self):
return str('%s object' % self.__class__.__name__)
which explains what you see. self.__class__.__name__ evaluates to "Conversation", hence you end up seeing "Conversation object" in the dropdown.
To change this behaviour you can override the __str__ method to get the desired value returned. One sample implementation is below. You could modify the method easily to do include any logic you want.
class Conversation(models.Model):
conversation = models.CharField(unique=True, max_length=150)
email_1 = models.ForeignKey(Usermie, to_field="email",
related_name="email_1_convo")
email_2 = models.ForeignKey(Usermie, to_field="email",
related_name="email_2_convo")
def __str__(self):
return self.conversation

Related

Django Admin Change View is stuck because of __str__ method of related field

I have this model in accounts.models:
from django.contrib.auth.models import User
class UserProfile(BaseModel):
user = models.OneToOneField(
User, related_name="user_profile", on_delete=models.CASCADE
)
# ...
def __str__(self) --> str:
return self.user.username
And the following in memberships.models:
class ExternalServiceProfileMembership(BaseModel):
created_at = models.DateTimeField()
expires_at = models.DateTimeField()
profile = models.ForeignKey(
"accounts.UserProfile",
on_delete=models.CASCADE,
related_name="ext_memberships",
)
plan = models.ForeignKey("memberships.MembershipPlan", on_delete=models.CASCADE)
ext_subscription_id = models.CharField(max_length=128)
When I try to access the admin view of an individual ExternalServiceProfileMembership object (for example: http://localhost:8000/admin/memberships/externalserviceprofilemembership/1/change/), the site gets stuck, eventually returning a 503. So I started out commenting out fields in the AdminModel, and once I remove profile, the object change view loaded fine.
I brought back profile into AdminModel but removed UserProfile's __str__() method, and it also worked. Which makes me think the whole issue is with this method; but I have no idea why.
Any help is appreciated!
On the change page for ExternalServiceProfileMembership, the profile dropdown displays the name of every user. This causes one extra query for every user in the dropdown.
The quick fix is to add 'profile' to readonly_fields, autocomplete_fields or raw_id_fields. These three options mean that a single profile is displayed on the change form, so there is only one extra query to fetch the user.
Another approach, which is more complicated, is to create a custom form that overrides the queryset to use select_related to fetch all of the users, then use that form in your model admin.

How to make existing model objects read-only but also be able to create a new one, using StackedInline class?

I have two models named 'Message' and 'Ticket'. Message has a Foreignkey to Ticket. I showed Messages of a Ticket in django admin, using StackedInline. But the problem is I want that the already created messages be read-only, while being able to create new message, too.
I've also check bunch of questions; like this or this. But none of the was helpful! Or at least, I could not get the clue!
This is my code:
models.py:
class Ticket(models.Model):
title = models.CharField(max_length=128)
#...
class Message(models.Model):
text = models.TextField()
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
attachment = models.FileField(upload_to=some_url_pattern)
sender = models.CharField(max_length=2, editable=False)
admin.py:
class MessageInline(admin.StackedInline):
model = Message
extra = 1
def get_readonly_fields(self, request, obj=None):
if obj:
return ['text', 'attachment']
else:
return []
#admin.register(Ticket)
class ResponderAdmin(admin.ModelAdmin):
fields = ['title']
inlines = [MessageInline]
As can be seen, I tried to achieve the goal by overriding get_readonly_fields but this is what happend:
the screenshot of admin page
As can be seen in the picture, every message inlines has been made read-only and I can not add a new message...
Can anyone help me with this issue?
I am assuming this is for admin.
Remove the user's SuperUser access but leave them with Staff access. Then use permissions to give them Add and access for the specific model but do not give them Update or Delete access. That should enable them to view the data without being able to change or delete it.

Can't disable ForeignKey referential integrity check in Django 1.9

I have a model with two entities, Person and Code. Person is referenced by Code twice, a Person can be either the user of the code or the approver.
What I want to achieve is the following:
if the user provides an existing Person.cusman, no further action is needed.
if the user provides an unknown Person.cusman, a helper code looks up other attributes of the Person (from an external database), and creates a new Person entity.
I have implemented a function triggered by pre_save signal, which creates the missing Person on the fly. It works fine as long as I use python manage.py shell to create a Code with nonexistent Person.
However, when I try to add a new Code using the admin form or a CreateView descendant I always get the following validation error on the HTML form:
Select a valid choice. That choice is not one of the available choices.
Obviously there's a validation happening between clicking on the Save button and the Code.save() method, but I can't figure out which is it. Can you help me which method should I override to accept invalid foreign keys until pre_save creates the referenced entity?
models.py
class Person(models.Model):
cusman = models.CharField(
max_length=10,
primary_key=True)
name = models.CharField(max_length=30)
email = models.EmailField()
def __unicode__(self):
return u'{0} ({1})'.format(self.name, self.cusman)
class Code(models.Model):
user = models.ForeignKey(
Person,
on_delete=models.PROTECT,
db_constraint=False)
approver = models.ForeignKey(
Person,
on_delete=models.PROTECT,
related_name='approves',
db_constraint=False)
signals.py
#receiver(pre_save, sender=Code)
def create_referenced_person(sender, instance, **kwargs):
def create_person_if_doesnt_exist(cusman):
try:
Person = Person.objects.get(pk=cusman)
except Person.DoesNotExist:
Person = Person()
cr = CusmanResolver()
Person_details = cr.get_person_details(cusman)
Person.cusman = Person_details['cusman']
Person.name = Person_details['name']
Person.email = Person_details['email']
Person.save()
create_Person_if_doesnt_exist(instance.user_id)
create_Person_if_doesnt_exist(instance.approver_id)
views.py
class CodeAddForm(ModelForm):
class Meta:
model = Code
fields = [
'user',
'approver',
]
widgets = {
'user': TextInput,
'approver': TextInput
}
class CodeAddView(generic.CreateView):
template_name = 'teladm/code_add.html'
form_class = CodeAddForm
You misunderstood one thing: You shouldn't use TextField to populate ForeignKey, because django foreign keys are populated using dropdown/radio button to refer to the id of the object in another model. The error you got means you provided wrong information that doesn't match any id in another model(Person in your case).
What you can do is: not using ModelForm but Form. You might have some extra work to do after you call form.is_valid(), but at least you could code up your logic however you want.

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Django - complex forms with multiple models

Django 1.1
models.py:
class Property(models.Model):
name = models.CharField()
addr = models.CharField()
phone = models.CharField()
etc....
class PropertyComment(models.Model):
user = models.ForeignKey(User)
prop = models.ForeignKey(Property)
text = models.TextField()
etc...
I have a form which needs to display several entries from my Property model each with a corresponding PropertyComment form to collect a user's comments on that property. In other words, allowing a User to comment on multiple Property instances on the same page.
This seems outside the intended usage of an Inline formset since it is multi-model to multi-model vs. single-model to multi-model. It seems like trying to iterate through the Property instances and create an inline formset for each is not only clunky, but I'm not even sure it could work.
Any ideas on where to start on this?
Have you thought about using the comment framework:
http://docs.djangoproject.com/en/dev/ref/contrib/comments/
If that doesnt work for you then maybe look into inlineformset_factory:
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets
from django.forms.models import inlineformset_factory
PropertyCommentFormSet = inlineformset_factory(Property, PropertyComment)
property= Property.objects.get(name=u'some property name')
formset = PropertyCommentFormSet(instance=property)
etc...

Categories

Resources