I am trying to show inactive users as "disabled" in my form's Select widget.
I have a worker who is a django user model.
models.py
class Task(models.Model):
worker = models.ForeignKey(settings.AUTH_USER_MODEL, models.DO_NOTHING, blank=True, null=True,related_name='worker')
It is represented by a ModelForm, using a subclass to display the full name of the user.
forms.py
class UserModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.get_full_name()
class TaskForm(forms.ModelForm):
worker = UserModelChoiceField(queryset=User.objects.filter(is_active=1).order_by('first_name'),widget=forms.Select(attrs={'class':'form-control'}),required=False)
class Meta:
model = Task
fields = ['worker']
Currently, the filter is_active=1 means that inactive users simply don't show in the list and where they've already been selected it appears as "---".
My ideal would be they appear but are greyed out so they are presented but can't be selected.
From reviewing https://djangosnippets.org/snippets/2453/
Which I found in
"Disabled" option for choiceField - Django
I was able to conclude that the subclass of the select should work. However I can't tell how to get between the queryset and the widget in order to achieve the expected result. Reading suggests the render method on the widget might be the way but I couldn't find examples of how to pass the information or exactly where create_option is invoked.
Related
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.
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.
I have a Django project that includes a model class with an optional self-referential ForeignKey field. A partial snippet:
class Site(models.model):
name = models.CharField(max_length=100)
parent_site = models.ForeignKey('self', null=True, blank=True)
I'm using the Django admin site to create new objects. For this class' admin form I'd like to disable the "Add another..." button next to the parent_site field (i.e. when you're creating a new site, you can't open the popup to create another new site as the parent).
I can't remove has_add_permission from the user, as they need it to be in the current add view. I don't mind removing the function from both add and change views, but limiting removal to the add view would be helpful.
I haven't been able to work out how to use the Inline field classes to achieve this, or formfield_for_foreignkey, or a custom ModelForm. Anyone got a solution more elegant than using JavaScript on a customised form template?
no css hacks add to admin class:
max_num=0
or try this in admin.py ( for older django versions):
class MODEL_ADMIN(admin.ModelAdmin):
class Media:
css = {'all': ('css/no-addanother-button.css',)}
The story begins with 2 models (User and ClientAccount) which are linked with an extra M2M model.
It is possible to create ClientAccount during editing User. The page will show a popup which allow you create a new ClientAccount. But the problem is: can I disable the foreign key fields of ClientAccount which is linking to User? This is quite confusing.
Code here:
class User(models.Model):
client_accounts = models.ManyToManyField('ClientAccount', related_name='+', through='UserClientAccountM2M', through_fields=('user', 'client_account'))
class ClientAccount(models.Model):
users = models.ManyToManyField('User', related_name='+', through='UserClientAccountM2M', through_fields=('client_account', 'user'))
class UserClientAccountM2M(models.Model):
user = models.ForeignKey(User, db_column='user_id')
client_account = models.ForeignKey(ClientAccount, db_column='client_id')
class UserAdmin(TimeLimitedAdmin):
class ClientAccountInline(admin.TabularInline):
model = ClientAccount.users.through
inlines = [
ClientAccountInline,
]
class ClientAccountAdmin(TimeLimitedAdmin):
class UserInline(admin.TabularInline):
model = ClientAccount.users.through
inlines = [
UserInline,
]
admin.site.register(User, UserAdmin)
I realise this is an old question, but I came across it as the most relevant question whilst looking to solve a similar problem (hide a FK inline only in the add popup). I'm sure you have left this problem well behind by now, but maybe others will find it useful.
I added the below to the ModelAdmin class that the popup is for. I check the request for the GET params only present when the popup dialog is launched, and if they are there, I loop through the inlines and remove the one I don't want.
def get_inline_instances(self, request, obj=None):
inline_instances = super().get_inline_instances(request, obj=None)
if '_to_field' in request.GET and '_popup' in request.GET:
# Popup dialog is open.
unwanted_inline = None
for inline in inline_instances:
inline_model_name = inline.opts.model.__name__
if inline_model_name == 'UnwantedModelName':
unwanted_inline = inline
if unwanted_inline:
inline_instances.remove(unwanted_inline)
return inline_instances
In your case, I would add the above to ClientAccountAdmin, removing the UserInline.
If you simply want to hide the m2m fields in the ClientAccount you can remove the line in the Admin.py
because there you explicitely say it should show the connection to the user in a tabularInline:
class ClientAccountAdmin(TimeLimitedAdmin):
class UserInline(admin.TabularInline):
model = ClientAccount.users.through
#inlines = [UserInline,]
There you say explicitely you want to have the M2M field from ClientAccount to User in an Inline, which you do not want. Get rid of it and the fields will disappear
EDIT:
The problem is that the "add..." link will always refer to the .../ClientAccount/add/?_popup=1 page which uses the default admin view for this model.
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...