Dynamical choices in model's field - django

I want my models to have order field, which will contain order of an item among all items of its kind.
And I want to use choices within that IntegerField, which would contain all the numbers of currently existing items in that table.
So it would need to be dynamic choices.
How do I load all existing "order" values of all existing items in a table, and use this list for choices?

It sounds like you want to build a manager for your model:
models.py
from django.db import models
class OrderManager(models.Manager):
def order_choices(self):
return [(i, i) for i in OrderModel.objects.values_list('order', flat=True)]
class OrderModel(models.Model):
objects = OrderManager()
order = models.IntegerField()
class Meta:
ordering = ['order']
def __unicode__(self):
return '%i' % self.order
forms.py
from django import forms
from yourapp.models import OrderModel
class OrderModelForm(forms.ModelForm):
order = forms.ChoiceField(choices=OrderModel.objects.order_choices())
class Meta:
model = OrderModel
admin.py
from django.contrib import admin
from yourapp.forms import OrderModelForm
from yourapp.models import OrderModel
class OrderModelAdmin(admin.ModelAdmin):
form = OrderModelForm
admin.site.register(OrderModel, OrderModelAdmin)
Edit
Managers are use to make general model queries without having an instance of a model object. If you don't understand the concept of managers, you can still refactor the code out of the manager class, stick it somewhere else and import that function across your code. Managers allow you to abstract custom general queryset that you can reuse. See more details https://docs.djangoproject.com/en/dev/topics/db/managers/
The code without the manager will look like
views.py or some other file
from app.models import OrderModel
def order_choices():
return [(i, i) for i in OrderModel.objects.values_list('order', flat=True)]
From anywhere in your code, if you want to reuse the above multiple times:
from app.views import oder_choices
order_choices()
as opposed to:
from app.models import OderModel
OrderModel.objects.order_choices()
If you only want to use the above once, you can leave it in the forms.py as shown in the other answer. It's really up to you on how you want to refactor your code.

Dont add the choices directly to the model, add them to a form represnting the model later, by overriding the field with a set of choices.
than, do something like:
class MyForm(..):
myfield_order_field = IntegerField(choices = [(i,i) for range(MyModel.objects.count)])
class Meta():
model = MyModel
if you want to use it in the admin, add to your Admin Class:
class MyModelAdmin(admin.ModelAdmin):
...
form = MyForm
it will override this field in the admin too.

Related

Add queryset as_manager to the AbstractUser model

I have a simple model User that simply extends the AbstractUser class with some extra fields. I tried adding "objects = UserQuerySet.as_manager() but is giving me an error "get_by_natural_key() is not defined" when i try to create a superuser. It seems that it is overwriting the regular user manager so i am losing the methods that it comes with. I tried renaming the objects field to something else so I wouldnt be overwriting the default one but it still the same error. Is there any way to simply add querysets without creating whole new manager class, extending the BaseUserManager, adding all of the default methods from scratch, and adding my custom queryset to it? I just want to keep the regular UserManager and just add querysets.
class UserQuerySet(QuerySet):
def more_ten(self):
return self.filter(points__gt=10)
class User(AbstractUser):
points = IntegerField(default=0)
tester = UserQuerySet.as_manager()
#objects = UserQuerySet.as_manager()
According to the docs, your custom user manager should inherit from BaseUserManager
class UserQuerySet(QuerySet):
def more_ten(self):
return self.filter(points__gt=10)
from django.contrib.auth.models import UserManager as OldUserManager
class UserManager(OldUserManager):
def get_queryset(self):
return UserQuerySet(model=self.model, using=self._db, hints=self._hints)
class User(AbstractUser):
objects = UserManager()

Django model managers.py and models.py

given the following situation:
models.py
from .managers import PersonManager
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
managers.py
from .models import Person
from django.db import managers
class PersonManager(models.Manager):
def create(self, person_dict):
new_person = Person(
first_name=person_dict['first_name']
last_name=person_dict['last_name'])
new_person.save()
How can I write my model manager to avoid circular import?
It is actually not working, my guess is that I would have to create my object inside my manager without refering to it as class Person, instead I should use a more general generic Django name. Any thoughts?
There are a few options here.
Firstly, you could define the model and the manager in the same file; Python has no requirement or expectation that each class is in its own file.
Secondly, you don't actually need to import the model into the manager. Managers belong to models, not the other way round; from within the manager, you can refer to the model class via self.model.
And finally, if that's all your manager is doing, there is no reason for it at all. Managers already have a create method; it takes keyword parameters, rather than a dict, but that just means you can call it with Person.objects.create(**person_dict).

Showing more than '__str__' for a Django foreign key field

I've two simple Django model classes,
models.py
from django.db import models
class ParentModel(models.Model):
small_text = models.CharField(max_length=20)
big_text = models.CharField(max_length=500)
def __str__(self):
return self.small_text
class ChildModel(models.Model):
parent = models.ForeignKey(ParentModel)
def __str__(self):
return '%s is my parent' % self.parent
admin.py
from django.contrib import admin
import models
admin.site.register(models.ChildModel)
admin.site.register(models.ParentModel)
So the default view is you see the 'small_text' in a select element in the admin section. What I'd love to be able to do is extend that so that there's another TextArea, or something else I can , underneath the select which changes as you choose a different Daddy.
I've looked into a few different ways to do this, but they all seem hella complicated for what with Django, I'd have thought should be an easy task. Any ideas?
If you're looking to be able to change ChildModel's properties while viewing the ParentModel in the admin, you should look into using an inline in the admin
If you're looking to have additional fields appear when viewing the index page in the admin for a model, then you'll want to add additional properties to the list_display property on the model's admin class.

django - Joining LogEntry to actual models

so i'm using the admin LogEntry object/table to log events in my app. I have a view where i'd like to display each LogEntry.
It would be really great if i could join the LogEntry with the actual objects they represent (so i can display attributes of the object inline with the log entry)
In theory this should be easy as we have the model type and id from the LogEntry but i can't figure out how to join them using a queryset.
i thought i could just grab all the ids of the different objects and make another dictionary for each object type and then join them somehow (maybe zip the lists together?) but that seems dumb and not very djano-ish/pythonic.
does anybody have better suggestions?
** edit **
just want to clarify am not looking to use admin, but roll a custom view and template.
As I know Django uses contenttypes framework to perform logging in admin. So you should create generic relation inside your model and then to show inlines in admin use GenericTabularInline and GenericStackedInline. Please consult with the article.
from django.contrib import admin
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.generic import GenericTabularInline
from django import forms
from some_app import models
from some_app.models import Item
class LogForm(forms.ModelForm):
class Meta:
model = LogEntry
class LogInline(GenericTabularInline):
ct_field = 'content_type'
ct_fk_field = 'object_id'
model = LogEntry
extra = 0
class ItemForm(forms.ModelForm):
class Meta:
model = Item
class ItemAdmin(admin.ModelAdmin):
form = ItemForm
inlines = [LogInline,]
admin.site.register(models.Item, ItemAdmin)
and you add to Item:
class Item(models.Model):
name = models.CharField(max_length=100)
logs = generic.GenericRelation(LogEntry)
this change won't create anything in your database, so there is no need to sync
Recent Django versions require to create a proxy for LogEntry:
from django.contrib import admin
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.generic import GenericTabularInline
class LogEntryProxy(LogEntry):
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
proxy = True
class LogInline(GenericTabularInline):
model = LogEntry
extra = 0
class ItemAdmin(admin.ModelAdmin):
inlines = [LogInline,]
admin.site.register(models.Item, ItemAdmin)

Reorder users in django auth

I have a model that has a ForeignKey to the built-in user model in django.contrib.auth and I'm frustrated by the fact the select box in the admin always sorts by the user's primary key.
I'd much rather have it sort by username alphabetically, and while it's my instinct not to want to fiddle with the innards of Django, I can't seem to find a simpler way to reorder the users.
The most straightforward way I can think of would be to dip into my Django install and add
ordering = ('username',)
to the Meta class of the User model.
Is there some kind of monkeypatching that I could do or any other less invasive way to modify the ordering of the User model?
Alternatively, can anyone thing of anything that could break by making this change?
There is a way using ModelAdmin objects to specify your own form. By specifying your own form, you have complete control over the form's composition and validation.
Say that the model which has an FK to User is Foo.
Your myapp/models.py might look like this:
from django.db import models
from django.contrib.auth.models import User
class Foo(models.Model):
user = models.ForeignKey(User)
some_val = models.IntegerField()
You would then create a myapp/admin.py file containing something like this:
from django.contrib.auth.models import User
from django import forms
from django.contrib import admin
class FooAdminForm(forms.ModelForm):
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
form = FooAdminForm
admin.site.register(Foo, FooAdmin)
Once you've done this, the <select> dropdown will order the user objects according to username. No need to worry about to other fields on Foo... you only need to specify the overrides in your FooAdminForm class. Unfortunately, you'll need to provide this custom form definition for every model having an FK to User that you wish to present in the admin site.
Jarret's answer above should actually read:
from django.contrib.auth.models import User
from django.contrib import admin
from django import forms
from yourapp.models import Foo
class FooAdminForm(forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwds):
super(FooAdminForm, self).__init__(*args, **kwds)
self.fields['user'].queryset = User.objects.order_by(...)
class FooAdmin(admin.ModelAdmin):
# other stuff here
form = FooAdminForm
admin.site.register(Foo, FooAdmin)
so the queryset gets re-evaluated each time you create the form, as opposed to once, when the module containing the form is imported.