I'm developing an application where I have a User creating a Document with some general content. I set up the model so the user can share that document with other users through a ManyToMany field shown below. The problem I have is the ManyToMany field shows all the users on my site as possible collaborators - whereas I want to only show them their team members. How would I go about doing that?
My models:
class Document(models.Model):
...
collaborators = models.ManyToManyField(User, related_name="doc_collaborators")
class User(models.Model):
...
team = models.CharField('team', max_length=50)
My forms:
class CreateDocForm(forms.ModelForm):
class Meta:
model = Document
exclude = ('created_at', 'updated_at', 'owner', 'slug')
One solution might be to override the queriset when the form is initialized:
class CreateDocForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['collaborators'].queryset = User.objects.filter([some filters])
...
Another solution is to use third-party modules: django-autocomplete-light
Related
I have the (simple, I suppose) need of having a situation like so: there are many profiles, and there are many ensembles, and each profile has to be able to be part of one or more ensembles. This is my code:
class Ensemble(models.Model):
ensembleName = models.CharField(max_length=200)
members = models.ManyToManyField('Profile', related_name='members')
def __str__(self):
return self.ensembleName
class Profile(models.Model):
ensemble = models.ForeignKey(Ensemble, on_delete=models.CASCADE, blank=True, null=True)
[...]
It all works well, but to an extent. From the Django administration I can, from the 'ensemble' page, select its members. I can also select, from the 'profile' page, which ensembles that profile belongs. The issue is: they are not synchronised: if I add a profile to an ensemble via the 'profile' page this is not reflected in the 'ensemble' page and the other way round, i.e. in the 'profiles details' page I don't see the ensemble to which I previously assigned that profile from the 'ensemble' page.
My form
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('image', 'role', 'skills', 'gender', etc...)
class EnsemblesForm(forms.ModelForm):
class Meta:
model = Ensemble
fields = ('ensemble_name',)
def __init__(self, *args, **kwargs):
super(EnsemblesForm, self).__init__(*args, **kwargs)
self.fields['ensemble_name'].queryset = (obj for obj in Ensemble.objects.all()) #This doesn't output anything
I have Django database with 2 models: DeviceModel and Device. Let's say, for example, DeviceModel object is "LCD panel" and Device object is "LCD panel №547". So these two tables have ManyToOne relationship.
class DeviceModel(models.Model):
name = models.CharField(max_length=255)
class Device(models.Model):
device_model = models.ForeignKey(DeviceModel)
serial_number = models.CharField(max_length=255)
Now I need to add some relations between DeviceModel objects. For example "LCD Panel" can be in "Tablet" object or in "Monitor" object. Also another object can be individual, so it doesn't link with other objects.
I decided to do this with ManyToMany relationship, opposed to using serialization with JSON or something like that (btw, which approach is better in what situation??).
I filled all relationships between device models and know I need to add relationship functional to Device table.
For that purpose I added "master_dev" foreignkey field pointing to 'self'. It works exactly as I need, but I want to restrict output in django admin panel. It should display only devices, that are connected through device_links. Current code:
class DeviceModel(models.Model):
name = models.CharField(max_length=255)
device_links = models.ManyToManyField('self')
class Device(models.Model):
device_model = models.ForeignKey(DeviceModel)
serial_number = models.CharField(max_length=255)
master_dev = models.ForeignKey('self', blank=True, null=True)
So, how can I limit output of master_dev field in admin panel?
There is a function "limit_choices_to", but I can't get it to work...
in forms.py:
def master_dev_chioses():
chioses = DeviceModel.objects.filter(do your connection filter here - so not all Devicemodels comes to choicefield)
class DeviceForm(forms.ModelForm):
class Meta:
model = Device
def __init__(self, *args, **kwargs):
super(Device, self).__init__(*args, **kwargs)
self.fields['master_dev'].choices = master_dev_chioses()
While there is no direct answer to my question about "limit_choices_to" function, I post solution that achieves desired output:
from django import forms
from django.contrib import admin
from .models import DeviceModel, Device
class DeviceForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DeviceForm, self).__init__(*args, **kwargs)
try:
linked_device_models = self.instance.device_model.device_links.all()
linked_devices = Device.objects.filter(device_model__in=linked_device_models)
required_ids = set(linked_devices.values_list("id", flat=True))
self.fields['master_dev'].queryset = Device.objects.filter(id__in=required_ids).order_by("device_model__name", "serial_number")
except:
# can't restrict masters output if we don't know device yet
# admin should edit master_dev field only after creation
self.fields['master_dev'].queryset = Device.objects.none()
class Meta:
model = Device
fields = ["device_model", "serial_number", "master_dev"]
class DeviceAdmin(admin.ModelAdmin):
form = DeviceForm
list_display = ('id', 'device_model', 'serial_number')
list_display_links = ('id', 'device_model')
search_fields = ('device_model__name', 'serial_number')
list_per_page = 50
list_filter = ('device_model',)
In my Django (1.6+) application, I have many Django models that point to (read only) DB views.
These models also contain foreign key relations.
Now if the Django application tries to delete the related fk-models, this will lead to DB errors ("Cannot delete from view") if I don't set cascade=DO_NOTHING on all foreign key fields.
Example:
class PersonView(models.Model):
person = models.ForeignKey(Person, db_column='fk_person', on_delete=DO_NOTHING)
class Meta:
db_table = 'view_persons'
managed = False
Since all my db-view-model-ForeignKey-fields should have cascade=DO_NOTHING by default, I'd like to create a DB-View model base class which will automatically set all foreign-key-fields to on_delete=DO_NOTHING, so I only need to care for inheriting from this model - otherwise it's easy to forget (and redundant) setting this attribute for all fields.
In the end I want to end up with a code like this:
class ViewModel(models.Model):
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(ViewModel, self).__init__(*args, **kwargs)
# How to set all foreign-key fields' on_delete attribute to "DO_NOTHING"?
class PersonView(ViewModel):
# no need to set on_delete anymore
person = models.ForeignKey(Person, db_column='fk_person')
class Meta:
db_table = 'view_persons'
managed = False
How can I alter Django model attributes in my base class to set all foreign key fields to on_delete=DO_NOTHING?
Well, you can monkey-patch models.ForeignKey but the more preferred method is to simply subclass ForeignKey:
class MyForeignKey(models.ForeignKey):
def __init__(self, *args, **kwargs):
super(MyForeignKey, self).__init__(*args, **kwargs)
self.on_delete = models.DO_NOTHING
Then you can use MyForeignKey instead of ForeignKey in your models.
Suppose I have car - suv/bus models.
I'd like to list all cars in the django admin (with name attribute).
When user clicks one of the car, it would go to the appropriate detail page of either suv or bus model.
How do I create a such admin list page?
class Car:
name = models.CharField()
class Meta:
abstract=True
class Suv(Car):
pass
class Bus(Car):
pass
Not sure this is the best approach, but sharing my solution here.
First, create a Database View
create view [app_label_model_name] as
select id, name, 1 as car_type
from suv
union
select id, name, 2 as car_type
from bus
order by something;
then create non-manged model
class PostBaseView(models.Model):
# this would be CarBaseView, I'm copying my actual code
id = models.IntegerField()
raw_html = models.TextField(null=True)
created_at = models.DateTimeField(primary_key=True)
post_type = models.IntegerField()
class Meta:
managed = False
then, from admin page, change the links based on subclass types.
class ChangeList(ChangeListDefault):
def url_for_result(self, result):
# pk = getattr(result, self.pk_attname)
id = result.id
app_label = result.get_app_label()
model_name = result.get_model_name()
return reverse('admin:%s_%s_change' % (app_label,
model_name),
args=(quote(id),))
class PostBaseViewAdmin(admin.ModelAdmin):
list_display = ['__str__', 'post_type_str']
class Meta:
model = PostBaseView
def get_changelist(self, request, **kwargs):
"""
Returns the ChangeList class for use on the changelist page.
"""
return ChangeList
admin.site.register(PostBaseView, PostBaseViewAdmin)
volla you have a admin that shows multiple subclasses at one list.
eugene's answer took me to the right direction. Thank you.
I'm not aware if there is a better way either, but in my case this approach solved the problem.
I already had a base class (Project) with some generic fields (SportProject and others), and specific classes that extends Project with their fields, and I wanted to have a single list of project, but wanted to have specific edit form for each project type.
In app/admin.py file, I did:
from django.contrib.admin.views.main import ChangeList as ChangeListDefault
from django.urls import reverse
class ProjectChangeList(ChangeListDefault):
def url_for_result(self, result):
app_label = result._meta.app_label.lower()
model_name = result.children._meta.object_name.lower()
return reverse('admin:%s_%s_change' % (app_label, model_name), args=(result.id,))
class ProjectDataAdmin(admin.ModelAdmin):
#...
def get_changelist(self, request, **kwargs):
"""
Returns the ChangeList class for use on the changelist page.
"""
return ProjectChangeList
In my model Project, I have a children property that returns the children instance (SportProject for instance). Not sure if there is another way to have this structure in Django.
I also had all classes registered in django-admin (Project and all children, as SportProject), to have django-admin pages for those classes. Thus, I'm using django-modeladmin-reorder to hide undesired links on django-admin main page.
Hope it helps someone.
You need to make your Car not an abstract model, as you have to have some base table for your vehicles.
class Car:
name = models.CharField()
class Meta:
abstract = False
class Suv(Car):
pass
class Bus(Car):
pass
class CarAdmin(admin.ModelAdmin):
model = Car
But here the easy things end. The admin doesn't have a built-in solution for your task. I suggest you overriding at least the CarAdmin.get_form() and CarAdmin.get_queryset() methods, though I'm not sure they are the only ones you have to customize.
I think this answer is applicable. ModelAdmin needs to know foreign key entries will be readonly, specified in a tuple called readonly_fields.
Using the problem that brought me here and there, I have (models.py):
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
def __str__(self):
return self.choice_text
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, default = 1)
class Meta:
abstract = True
class Vote(Answer):
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
def answer(self):
return self.choice
def __str__(self):
return self.choice.choice_text
And (admin.py):
class VoteAdmin(admin.ModelAdmin):
#list_display = ('Answer.question.question_text', 'Answer.User.user_id', 'Choice.choice_text')
readony_fields = ('question', 'user')
list_display = ('question', 'user', 'choice')
fieldsets = [
('Question', {'fields': ['question']}),
('User', {'fields': ['user']}),
('Vote', {'fields' : ['choice']}),
]
Hope this proves useful to future searchers.
I have in my models.py
class Business(models.Model):
industry = models.models.ManyToManyField(Industry)
in forms.py
class BusinessForm(forms.ModelForm):
class Meta:
model = Business
When I render the form, the industry names appear in a multiple select box. What do I do to make the industry names in alphabetical order?
There are several ways:
You can override the queryset ordering on a per-form basis, set the ordering meta class option, or override the model manager queryset with an ordering method.
Override global model manager queryset
class IndustryManager(models.Manager):
def get_query_set(self):
return (
super(IndustryManager, self)
.get_query_set()
.order_by('name')
)
class Industry(models.Model):
name = models.CharField(max_length=128)
objects = IndustryManager()
Specify global meta option ordering
class Industry(models.Model):
name = models.CharField(max_length=128)
class Meta:
ordering = ['name']
Per form ordering
class MyForm(forms.ModelForm):
class Meta:
model = Business
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['industry'].queryset = Industry.objects.order_by('name')
There's also a shortcut called formfield_for_manytomany if you are dealing with the django admin.
I like this method:
class BusinessForm(forms.ModelForm):
class Meta:
model = Business
industry = forms.ModelMultipleChoiceField(
queryset=Industry.objects.order_by('name'))
I like it, since it does not alter the database model, and since it is declarative (less programming).