django multiple choice model problems - django

I have two diferents problems with multple choices in models.
The first, i'm trying to do a multiple choice so the user can pick one or more days of the week:
DAYS_CHOICES = (
(1, _('Monday')),
...
(7, _('Sunday')),
)
...
day = models.ManyToManyField('day', choices=DAYS_CHOICES)
The second problem:
I want to make a ManyToMany Relation with a model define in other model:
First (Import to the model):
from events.models import Category
Second (The field related to the model):
type = models.ManyToManyField('Category', null=True, blank=True)
I get this error on syncdb:
Error: One or more models did not
validate: situ.situ: 'day' has an m2m
relation with model day, which has
either not been installed or is
abstract.
situ.situ: 'type' has an m2m relation
with model Category, which has either
not been installed or is abstract.

you could use :
day = forms.ModelMultipleChoiceField(queryset=Day.objects.all())

Unfortunately, the ManyToMany relation only works for relations with other models, not values from a choices set. Django does not provide a built in multiple choice model field type. However, I have used this snippet in the past when using a multiple select choices field: http://www.djangosnippets.org/snippets/1200/
This encodes the multiple selected options into a comma-separated list stored in a CharField, which works great, unless you need to do some sort of join or something on the selections. If you need to do that, you will have to define a new Day model that you can use the ManyToManyField on.
The second problem, I believe, is just the result of the first--if you clear up that issue, you'll be ok.

For the first part of your questions. You should be using a MultipleChoiceField
DAYS_CHOICES = (
(1, _('Monday')),
...
(7, _('Sunday')),
)
...
days = forms.MultipleChoiceField(choices=DAYS_CHOICES)
http://docs.djangoproject.com/en/dev/ref/forms/fields/#multiplechoicefield
This will yield a list of Unicode objects.
For the second problem, You need to either include the app name in the abstract declaration of the model in the m2m field or not declare it abstractly.
type = models.ManyToManyField(Category, null=True, blank=True)
or
type = models.ManyToManyField('events.Category', null=True, blank=True)
If the Category model was defined later in the same app in the models.py you could leave it Category but since it is in another app, you need to specify the app name.

Related

Django tabularInline 'categories.Category_children_ids' has more than one ForeignKey to 'categories.Category'. You must specify a 'fk_name' attribute

I'm want to create nested categories, model work fine.
class Category(models.Model):
category_name = models.CharField(max_length=100)
children_ids = models.ManyToManyField(
"Category", blank=True, related_name="categories"
)
...etc
but, when i add inline for admin panel
class ChildrensInline(admin.TabularInline):
model = Category.children_ids.through
compiler shows me error:
'categories.Category_children_ids' has more than one ForeignKey to 'categories.Category'. You must specify a 'fk_name' attribute.
I also try fk_name='categories', and columns name inside Category_children_ids table, but it not work
The value for fk_name has to be the name of the class (lower-case), prefixed with either "from_" or "to_", depending on your needs. So in your case, it has to be either fk_name='from_category' or fk_name='to_category':
class ChildrensInline(admin.TabularInline):
model = Category.children_ids.through
fk_name='from_category' # or: 'to_category'
Generally, a quick way to figure such things out is to (transiently) place a simple print(model.__dict__) pretty much anywhere in your code, here e.g. right after the second line. Then all fields of model will be shown in the console output.

Filter distinct objects from ManyToMany within ManyToMany

I have following Models in my Django DRF app.
class FilterValue(models.Model):
code = models.Charfield(…)
class ProductVariant(models.Model):
filters = models.ManyToManyField("FilterValue", blank=True, …)
class Product(models.Model):
variants = models.ManyToManyField("ProductVariant", blank=True, …)
category = models.ForeignKey("Category", blank=True)
And I’m trying to define function on Category model which will return all objects of FilterValue attached to all ProductVariants of all Products that are assigned in the category.
Since I’ll have loads of different ProductVarints I can’t get away with nested loops since it’d be really slow.
I had multiple attempts to solve it.
I can of course get all Products within a Category using:
products = Product.objects.filter(category=self)
But then I get stuck on the fact that I actually need to filter nested ManyToMany object since I need all ProductVariants of all Products in the QuerySet and then - in the another level I need all ManyToMany FilterValue objects of each ProductVariant.
Thank you.
Here is the documentation on how to query many-to-many relationships.
Try this to solve your problem:
filters = FilterValues.objects.filter(productvariant_set__in = ProductVariant.objects.filter(product_set__in = Product.objects.filter(category=your_category)))

Django (DRF) ManyToMany field choices / limit

Working with Django REST Framework I am wondering if it's possible to limit the choices / options of a ManyToMany field on a model to a specific QuerySet?
Using the models below (scroll down to see models), I am curious if it's possible to define this limit in the definition of the model, to achieve the following:
# Having the following Employee instance
emp = Employee(...)
# Should return only the instances with value 'case' in EmployeeSubstitute.type field
emp.substitute_case.all()
# Should return only the instances with value 'phone' in EmployeeSubstitute.type field
emp.substitute_phone.all()
Models:
class Employee(models.Model):
substitute_case = models.ManyToMany(through=EmployeeSubstitute, ...)
substitute_phone = models.ManyToMany(through=EmployeeSubstitute, ...)
class EmployeeSubstitute(models.Model):
from = models.ForeignKey(Employee, ...)
to = models.ForeignKey(Employee, ...)
type = models.CharField(choices=..., ...) # choose between type 'case' and 'phone'
I see that there's the limit_choices_to parameter, but that's not what I am looking for, since that only effects the options shown when using a ModelForm or the admin.
Well, ManyToManyField returns related objects and as docs state
By default, Django uses an instance of the Model._base_manager manager
class when accessing related objects (i.e. choice.question), not the
_default_manager on the related object. This is because Django needs to be able to retrieve the related object, even if it would otherwise
be filtered out (and hence be inaccessible) by the default manager.
If the normal base manager class (django.db.models.Manager) isn’t
appropriate for your circumstances, you can tell Django which class to
use by setting Meta.base_manager_name.
Base managers aren’t used when querying on related models, or when
accessing a one-to-many or many-to-many relationship. For example, if
the Question model from the tutorial had a deleted field and a base
manager that filters out instances with deleted=True, a queryset like
Choice.objects.filter(question__name__startswith='What') would include
choices related to deleted questions.
So if I read it correctly, no, it's not possible.
When you do queries and have through in your ManyToManyField, Django complains you should run these queries on your through model, rather than the "parent". I can't find it in the docs but I remember seeing it a few times.
substitute_case and substitute_phone is something that belongs to substitute and it is it's type. So just do that instead of creating those columns in Employee.
from django.db import models
class SubstituteTypes(models.TextChoices):
case = "case", "case"
phone = "phone", "phone"
class EmployeeSubstituteQueryset(models.QuerySet):
def from_employee(self, e):
return self.filter(_from=e)
def case(self):
return self.filter(type=SubstituteTypes.case)
def phone(self):
return self.filter(type=SubstituteTypes.phone)
class Employee(models.Model):
substitute = models.ManyToManyField(through='EmployeeSubstitute', to='self')
class EmployeeSubstitute(models.Model):
_from = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='a')
to = models.ForeignKey(Employee, on_delete=models.PROTECT, related_name='b')
type = models.CharField(choices=SubstituteTypes.choices, max_length=5, db_index=True)
objects = EmployeeSubstituteQueryset.as_manager()
Then, once you get your emp object (or only its id), you can do
EmployeeSubstitute.objects.from_employee(emp).case().all()
which is designed in Django philosophy.

When splitting a Django model, How to keep ForeignKey and ManyToMany relationships during data migration?

I have a Django model that is doing way too much. Here's an abbreviated example of the model. Basically, it can represent four different Entity types, and there are recursive ForeignKey and ManyToMany relationships that point to other entities.
This project is currently using Django 1.8.x and Python 2.7.x, but I can upgrade those if the solution requires it.
class Entity(models.Model):
"""
Films, People, Companies, Terms & Techniques
"""
class Meta:
ordering = ['name']
verbose_name_plural = 'entities'
# Types:
FILM = 'FILM'
PERSON = 'PERS'
COMPANY = 'COMP'
TERM = 'TERM'
TYPE_CHOICES = (
(FILM, 'Film'),
(PERSON, 'Person'),
(COMPANY, 'Company'),
(TERM, 'Term/Technique'),
)
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
type = models.CharField(max_length=4, choices=TYPE_CHOICES, default=FILM)
slug = models.SlugField(blank=True, unique=True, help_text="Automatically generated")
name = models.CharField(max_length=256, blank=True)
redirect = models.ForeignKey('Entity', related_name='entity_redirect', blank=True, null=True, help_text="If this is an alias (see), set Redirect to the primary entry.")
cross_references = models.ManyToManyField('Entity', related_name='entity_cross_reference', blank=True, help_text="This is a 'see also' — 'see' should be performed with a redirect.")
[... and more fields, some of them type-specific]
I realize this is rather messy, and I'd like to remove 'type' and make an EntityBase class that abstracts out all of the common fields, and create new Film, Person, Company, and Term models that inherit from the EntityBase abstract base class.
Once I create the new models, I think I understand how to write the data migration to move all of the field data over to the new models (iterate over objects from Entity, filtered via type, create new objects in the appropriate new model)... except the ForeignKey and ManyToMany relationships. Maybe I'm thinking about this the wrong way, but how can I transfer those relationships when, during the migration, the new object that the relationship points to may not exist yet?
I suspect this may mean a multi-step migration, but I haven't quite worked out the right way to do it.
There is nothing magical about m2m and fk fields. This is the procedure that I would follow... It might be a bit blunt, but will get the job done:
Make a BACKKKUPPPPPPppp of the database!!
Make another backup!
Create the new model and migration
Write a new data migration that will manually iterate over existing models and update the new model, one-by-one. Don't be afraid of the for loop here, unless you have millions of entries in db.
Delete redundant models and/or fields, make migration for this.
Run those migrations :)
In practice, this means a lot of restoring from the "BACKKKUPPPPPPppp" until the migrations are just right.
One little thing to take care of:
M2m fields cannot get any value if model is not yet saved (because model gets its ID on first save). I would do something like, in the manual migration:
new_instance = NewModel()
new_instance.somefield = "whatever"
new_instance.meaning = 42
....
new_instance.save()
new_instance.that_m2m_field.add(some_related_obj)
Of course, make sure you read the docs in detail, especially that bit about importing the model class - you can't just import it from myapp.models import MyModel, instead do:
MyModel = apps.get_model("myapp", "MyModel")
One possible tripping stone might be the model inheritance that you plan to introduce. Generally, you will want to operate on the child model, and access the parent from there as / if needed. Parent can be accessed via the implicit ptr attribute - in your example it would be entitybase_ptr or something similar (that is just a OneToOne field). Going in the other direction, however, (from parent to unknown child) is not as straightforward, because parent doesn't a priori know what is the class of its child.

How to remove redundant ID field in auto-generated ManyToMany table in Django?

I have two classes in my models.py file:
class Person:
person_name = models.CharField(max_length = 50)
class Course:
course_name = models.CharField(max_length = 50)
course_person = models.ManyToManyField(Person)
In my modified example, one person is takes many courses and one course is taken by many people, hence ManyToMany.
When I let Django auto-generate my table, I get an extra ID field.
I want the autogenerated person_course manytomany table to consist of the two composite keys person_id and course_id only. Note: Both of them are auto-generated, auto-incremented fields.
I have also tried defining my ManyToMany class and attempted to link the fields using the keyword through=, but that did not help.
I have asked Google but without much help. Many some geniuses among you can provide some hint :)
Django currently does not support composite primary key by default
What you can do instead is keep the auto generated id as the (surrogate) primary key and then define a unique_together relationship in the through table.
class Meta:
unique_together = (course, person)
This way, you can guarantee unique entries in the through table, and when you reference the id it is the equivalent of referencing the unique (course, person) which is what we want anyways.
There are some third party apps that implement this feature if you want. However, unless an absolute necessity (like a legacy system support), I would just keep it simple and implement unique_together.
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)