GithubHelp home page GithubHelp logo

django-typed-models's Introduction

django-typed-models

example workflow

Intro

django-typed-models provides an extra type of model inheritance for Django. It is similar to single-table inheritance in Ruby on Rails.

The actual type of each object is stored in the database, and when the object is retrieved it is automatically cast to the correct model class.

Licensed under the New BSD License.

Features

  • Models in querysets have the right class automatically
  • All models subclassing a common base are stored in the same table
  • Object types are stored in a 'type' field in the database
  • No extra queries or joins to retrieve multiple types

Usage:

An example says a bunch of words:

# myapp/models.py

from django.db import models
from typedmodels.models import TypedModel

class Animal(TypedModel):
    """
    Abstract model
    """
    name = models.CharField(max_length=255)

    def say_something(self):
        raise NotImplemented

    def __repr__(self):
        return u'<%s: %s>' % (self.__class__.__name__, self.name)

class Canine(Animal):
    def say_something(self):
        return "woof"

class Feline(Animal):
    mice_eaten = models.IntegerField(
        default = 0
        )

    def say_something(self):
        return "meoww"

Later:

>>> from myapp.models import Animal, Canine, Feline
>>> Feline.objects.create(name="kitteh")
>>> Feline.objects.create(name="cheetah")
>>> Canine.objects.create(name="fido")
>>> print Animal.objects.all()
[<Feline: kitteh>, <Feline: cheetah>, <Canine: fido>]

>>> print Canine.objects.all()
[<Canine: fido>]

>>> print Feline.objects.all()
[<Feline: kitteh>, <Feline: cheetah>]

You can actually change the types of objects. Simply run an update query:

Feline.objects.update(type='myapp.bigcat')

If you want to change the type of an object without refreshing it from the database, you can call recast:

kitty.recast(BigCat)
# or kitty.recast('myapp.bigcat')
kitty.save()

Listing subclasses

Occasionally you might need to list the various subclasses of your abstract type.

One current use for this is connecting signals, since currently they don't fire on the base class (see #1)

    for sender in Animal.get_type_classes():
        post_save.connect(on_animal_saved, sender=sender)

Django admin

If you plan to use typed models with Django admin, consider inheriting from typedmodels.admin.TypedModelAdmin. This will hide the type field from subclasses admin by default, and allow to create new instances from the base class admin.

from django.contrib import admin
from typedmodels.admin import TypedModelAdmin
from .models import Animal, Canine, Feline

@admin.register(Animal)
class AnimalAdmin(TypedModelAdmin):
    pass

@admin.register(Canine)
class CanineAdmin(TypedModelAdmin):
    pass

@admin.register(Feline)
class FelineAdmin(TypedModelAdmin):
    pass

Limitations

  • Since all objects are stored in the same table, all fields defined in subclasses are nullable.
  • Fields defined on subclasses can only be defined on one subclass, unless the duplicate fields are exactly identical.

Requirements

  • Django 3.2+
  • Python 3.6+

django-typed-models's People

Contributors

adamchainz avatar bogdanstaicu avatar craigds avatar dandavison avatar krzysiekj avatar mad-anne avatar magopian avatar mcosti avatar olivierdalang avatar sloria avatar sobolevn avatar souliane avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-typed-models's Issues

Django 1.10: deferred_class_factory no longer exists

I noticed that after accidentally upgrading to django 1.10 that django-typed-models was squawking at an invalid import. Looks like Django folks either moved it or removed it.

Unhandled exception in thread started by <function wrapper at 0x03EEC970>
Traceback (most recent call last):
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\utils\autoreload.py", line 226, in wrapper
    fn(*args, **kwargs)
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\core\management\commands\runserver.py", line 113, in inner_run
    autoreload.raise_last_exception()
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\utils\autoreload.py", line 249, in raise_last_exception
    six.reraise(*_exception)
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\utils\autoreload.py", line 226, in wrapper
    fn(*args, **kwargs)
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\__init__.py", line 27, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\apps\registry.py", line 108, in populate
    app_config.import_models(all_models)
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\django\apps\config.py", line 199, in import_models
    self.models_module = import_module(models_module_name)
  File "C:\Python27\Lib\importlib\__init__.py", line 37, in import_module
    __import__(name)
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\src\backend\news\models.py", line 10, in <module>
    from typedmodels.models import TypedModel
  File "C:\Users\dobbyn\PycharmProjects\lantasy\website2\env\lib\site-packages\typedmodels\models.py", line 12, in <module>
    from django.db.models.query_utils import DeferredAttribute, deferred_class_factory
ImportError: cannot import name deferred_class_factory

Duplicate exactly identical `ForeignKey`, `ManyToMany`, `OneToOne` fields on child classes raise `AppRegistryNotReady` error

The Limitations section of the documentation says "Fields defined on subclasses can only be defined on one subclass, unless the duplicate fields are exactly identical".

In testapp/models.py there's an example of two child classes, Developer and Manager, which inherit from the base class Employee and have an exactly identical CharField, along with the comment Adds the _exact_ same field as Developer. Shouldn't error.

I've made a test project using Django 3.2.12 to try out django-typed-models, and I'm finding that exactly identical ForeignKey, ManyToMany, and OneToOne fields on child classes throw an AppRegistryNotReady error, whereas I was expecting them to be de-duplicated like the CharField example.

Here's my models.py:

from django.db import models
from typedmodels.models import TypedModel


class Related(models.Model):
    name = models.CharField(max_length=128)


class Base(TypedModel):
    name = models.CharField(max_length=128)


class ChildA(Base):
    child_a_field = models.CharField(max_length=128, null=True)
    related = models.ForeignKey(
        Related,
        on_delete=models.CASCADE,
        null=True
    )


class ChildB(Base):
    child_b_field = models.CharField(max_length=128, null=True)
    related = models.ForeignKey(
        Related,
        on_delete=models.CASCADE,
        null=True
    )


class ChildC(Base):
    child_c_field = models.CharField(max_length=128, null=True)

When I run python manage.py makemigrations I get:

Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()
  File "manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
    django.setup()
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/apps/config.py", line 301, in import_models
    self.models_module = import_module(models_module_name)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 848, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/Users/derth/PycharmProjects/test_dtm/test_dtm/test_fk/models.py", line 22, in <module>
    class ChildB(Base):
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/typedmodels/models.py", line 130, in __new__
    if duplicate_field.deconstruct()[1:] != field.deconstruct()[1:]:
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/db/models/fields/related.py", line 875, in deconstruct
    name, path, args, kwargs = super().deconstruct()
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/db/models/fields/related.py", line 594, in deconstruct
    swappable_setting = self.swappable_setting
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/db/models/fields/related.py", line 374, in swappable_setting
    return apps.get_swappable_settings_name(to_string)
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/apps/registry.py", line 289, in get_swappable_settings_name
    for model in self.get_models(include_swapped=True):
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/apps/registry.py", line 179, in get_models
    self.check_models_ready()
  File "/Users/derth/PycharmProjects/test_dtm/venv/lib/python3.8/site-packages/django/apps/registry.py", line 141, in check_models_ready
    raise AppRegistryNotReady("Models aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.

The same stack trace also happens with a ManyToManyField or OneToOneField.

As a workaround, I tried deleting the related field from the ChildB model. This allowed me to instantiate ChildB objects with a related field like so:

>>> related = Related.objects.create(name="Related object")
>>> child_b = ChildB.objects.create(name="Child B", child_b_field="My child b field", related=related)

The related field is then accessible (along with all the other columns in the Base table) if you query Base objects:

>>> Base.objects.all().values()
<QuerySet [{'id': 1, 'type': 'test_fk.childb', 'name': 'Child B', 'child_a_field': None, 'related_id': 1, 'child_b_field': 'My child b field', 'child_c_field': None}]>

But it's not accessible if you query ChildB objects:

>>> ChildB.objects.all().values()
<QuerySet [{'id': 1, 'type': 'test_fk.childb', 'name': 'Child B', 'child_b_field': 'My child b field'}]>

It would be great to be able to have the same ForeignKey, ManyToMany or OneToOne field on multiple (but not all) children of a base class.

Make it possible to extend TypedModel

I'd like to extend TypedModel with another abstract model class.

I've tried the following:

from typedmodels.models import TypedModel
from typedmodels.models import TypedModelMetaclass


class MyTypedModel(TypedModel, metaclass=TypedModelMetaclass):
    pass

    class Meta:
        abstract = True

Then I make a model named Place inherit from MyTypedModel and from another abstract model class (Module, which adds a number of model fields such as slug):

class Place(MyTypedModel, Module):
    """Where something has happened."""

    name = models.CharField(
        verbose_name=_('name'),
        blank=True,
        max_length=NAME_MAX_LENGTH,
        unique=True,
    )
    location = models.ForeignKey(
        to='self',
        related_name='places',
        blank=True,
        null=True,
        on_delete=models.PROTECT,
    )
    preposition = models.CharField(max_length=2, choices=PREPOSITION_CHOICES, default='in')

    class Meta:
        unique_together = ['name', 'location']

    objects = TypedModelManager()
    slug_base_fields = ('string',)

However, this inheritance results in a TypeError:

File "/usr/local/lib/python3.9/site-packages/typedmodels/models.py", line 139, in __new__
cls = super(TypedModelMetaclass, meta).__new__(
File "/usr/local/lib/python3.9/site-packages/django/db/models/base.py", line 177, in __new__
raise TypeError(
TypeError: Abstract base class containing model fields not permitted for proxy model 'Place'.

I'm on Django 3.2.

Any direction on this would be much appreciated!

Support for inlines in Django Admin?

Haven't been able to dig through Admin to find out how to apply the same kind of patching to inline objects as is done in the included ModelAdmin subclass.

If anyone has any pointers would be happy to try further.

Subclass form validation raises FieldDoesNotExist

class Parent(TypedModel):
    a = models.CharField(...)

class Child1(Parent):
    b = models.OneToOneField(...)

class Child2(Parent):
    pass

Creating a Child2 via the admin, using a model form, raises:
django.core.exceptions.FieldDoesNotExist: Child2 has no field named 'b'

The problem comes from db.models.base.Model._get_unique_checks() that gets _meta.local_fields from parent classes. Parent having all fields from all subclasses, db.models.base.Model._perform_unique_checks() raises that error.

I fixed that problem as follows but maybe someone will have a better solution…

class Parent(TypedModel):
    def _perform_unique_checks(self, unique_checks):
        clean_unique_checks = []
        for model_class, unique_check in unique_checks:
            clean_unique_check = []
            for field_name in unique_check:
                try:
                    clean_unique_check.append(self._meta.get_field(field_name))
                except FieldDoesNotExist:
                    pass
            if len(clean_unique_check):
                clean_unique_checks.append((model_class, clean_unique_check))

        return super()._perform_unique_checks(clean_unique_checks)

Fields on some but not all subclasses

I was hoping this was possible:

class Animal(TypedModel):
    name = models.CharField(max_length=100)

class Cat(Animal):
    meow_volume = models.IntegerField()
    owner_name = models.CharField(max_length=100)

class Dog(Animal):
    bark_volume = models.IntegerField()
    owner_name = models.CharField(max_length=100)

class Bear(Animal):
    growl_volume = models.IntegerField()

But I get:

django.db.utils.ProgrammingError: column "owner_name" specified more than once

I know I could move 'owner_name' on to the base class but then Bears have an owner - which they shouldn't have.

Is there any way that the duplicate fields could be magically recognised and de-dupped? Obviously they would have to be identical - that would be a reasonable constraint to impose on this kind of arrangement.

Signals on parent class don't propagate to child classes

post_save.connect(func, sender=ParentModel)
...
ChildModel().save()

The signal gets connected to the parent class, and then never gets fired when instances are saved, because they're never saved via the parent model.

This seems craycray. A workaround is to register the signals from all the child classes:

for sender in ParentModel._typedmodels_registry.values():
    post_save.connect(func, sender=sender)

But perhaps we can do better somehow?

Type stubs?

Type stubs for this library would be great so that it doesn't interfere with usage of static type checkers like pyright. Currently, since there's no stub file, static type checkers are unable to fully determine types of attributes of models that inherit from TypedModel.

Screenshot 2023-11-23 at 9 51 37 PM Screenshot 2023-11-23 at 9 53 35 PM

Migrations are generated without changing available types

Issue

Sometimes migrations are generated without changing the content of the list of declared types. The only thing that changes is the ordering of choices declared in the type field.

Version

This issue happens with the last version 0.12.0.

Reason

The issue is caused by the fact that choices are populated dynamically (here) based on the ordering of loaded apps. If we will import a model inherited from the base typed model in another place or change the ordering of imports usages, it may affect the ordering of choices. As a result, Django will want to generate a new migration file with the updated ordering.

Proposed solution

We may keep choices sorted alphabetically so Django won't detect any changes if ordering would be the only thing changed.

Django 3 support and package maintenance?

Hi,

I was wondering if this project is still maintained, I noticed pull requests have not been merged for quite some time and a lot of (very) old issues are not being closed.

Also this package is not compatible anymore with Django 3 since Django 3 dropped build-in support for django.utils.six

I think this project is great work, the only way holding me back in using it in my projects is the low activity. It would be nice to get a little insight about the future plans on this package.

Warn (or solve) if there are duplicate child class field names

This issue is a copy of KrzysiekJ#1 by @mnieber.

The following does not work because the name "request_message" is duplicated in two child classes of the base class Message, which produces a conflict later on when querying the database.

class Message(TypedModel):
    pass

class AcceptMessage(Message):
    request_message = models.ForeignKey('self', null=True, blank=True, related_name='+')

class DeclineMessage(Message):
    request_message = models.ForeignKey('self', null=True, blank=True, related_name='+')

This can be solved by moving request_message to the base class but this is not always what you want (perhaps a third class inheriting from Message does not have a request_message field).

It would be nice if TypedModel would at least raise an exception in this case so that the programmer becomes aware of the problem.

django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.

Is this compatible with the django 1.8? I have tried numerous settings but cannot get the example listed in the readme to work. The main issue seems to be that you cannot reference the base typed model from its subclasses within models.py.

Here is the full traceback: https://gist.github.com/davidcroda/ac493a0ad367a56aa977#file-gistfile1-txt-L18

This is a fresh django project, with a single app, using the example models.py from the readme.

Creation of instance in migration does not fill type field

Let's say we will have code like that in migration:

ChildModel = apps.get_model('app', 'ChildModel')
ChildModel.objects.create(field='a')

The instance of parent model is saved in DB with type field set as empty string.

While there is probably nothing we can do about it (as apps.get_model just looks at the table specification on given point in time) I think it's worth mention somewhere about this limitation as this might lead to hard to debug errors.

The workaround is to always set type in migration, i.e.:

ChildModel = apps.get_model('app', 'ChildModel')
ChildModel.objects.create(field='a', type='app.childmodel')

Monkey patching Python and XML serializers

Why should we use the name of the base class and not the name of the definite child?

_python_serializer_get_dump_object = _PythonSerializer.get_dump_object
def _get_dump_object(self, obj):
if isinstance(obj, TypedModel):
return {
"pk": smart_text(obj._get_pk_val(), strings_only=True),
"model": smart_text(getattr(obj, 'base_class', obj)._meta),
"fields": self._current
}
else:
return _python_serializer_get_dump_object(self, obj)

admin integration

Just testing out the app, looks very useful.

Out of the box, it's a bit counter intuitive when adding child classes in django admin because "type" field appears as a select. This means you click on "Add a new Feline" and then you need to select Feline again.

Would it be an option to hack "editable=False" in the child classes fields, so that the select doesn't appear by default ? Alternatively, a ModelAdmin mixin could be included in the app to deal with this.

Fields with default values shouldn't need to be nullable

class BaseModel(TypedModel):
    some_fields = ...
class MyModel(BaseModel):
    field = models.BooleanField(default=False)

result

SystemCheckError: System check identified some issues:

ERRORS:
app.BaseModel.field: (fields.E110) BooleanFields do not accept null values.
	HINT: Use a NullBooleanField instead.

Make _typedmodels_registry public

If you want to render a ChoiceField with all available subclasses of a TypedModel, you need _typedmodels_registry.

I think it should be part of the public API: I think the leading underscore should be removed.

Allow for custom field name rather than `type`

One limitation of this library as written today is that it reserves type as a field to hold the model type. When migrating an existing codebase in which type is already reserved, this causes difficulties.

It would be great if there were a way to specify a different field to use to store the polymorphic type in the database.

signals for derived class have base class as source when cascading

Given this doc-item hierarchy:

class BaseDoc(TypedModel):
   ...
class BaseDocItem(TypedModel):
   doc = models.ForeignKey(BaseDoc)
class MyDoc(BaseDoc):
    ...
class MyItem(BaseDocItem):
    ...

and post_delete signal handler for MyItem
then:
item.delete() fires post_delete signal with source=MyItem
but doc.delete() fires post_delete signal with source=BaseDocItem
expected result:
without TypedModel post_delete is always fired with source=MyItem

Related name on subclasses is not used

Consider this code example:

class Review(TypedModel):
     rating = models.IntegerFIeld()


class SpecificReview(Review):
     article = models.OneToOneField(Article, related_name="review")


SpecificReview.objects.create(rating=5, article=article)

article.reload_from_db()

print(article.review)

This will fail at the last line, "review" is not recognized as a related name.

I'm assuming this has something to do with how proxy models work, but I'm not sure.

ModelForm tries INSERT, but UPDATE would be correct

If you use ModelForm of a django typed model, the form of a child class does not see the objects form the parent class.

In the following example there is already a config if type BaseConfig. If
I save the SpecialConfigForm I get the following exception:

class BaseConfig(typedmodels.TypedModel):
    unique_fk=models.ForeignKey(ModelWhichShouldBeConfigured, unique=True)
class SpecialConfig(BaseConfig):
    pass
class SpecialConfigForm(forms.ModelForm):
    class Meta:
        model=SpecialConfig
        exclude=['type']

If there is already a BaseConfig, then SpecialConfigForm tries an INSERT, but an UPDATE should happen.

  File "/home/foo_eins_d/src/foo-myapp/foo_myapp/tests/unit/test_forms.py", line 26, in test_myapp_special_config_form__create
    form.save()
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/forms/models.py", line 370, in save
    fail_message, commit, construct=False)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/forms/models.py", line 87, in save_instance
    instance.save()
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/typedmodels/models.py", line 365, in save
    return super(TypedModel, self).save(*args, **kwargs)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/models/base.py", line 546, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/models/base.py", line 591, in save_base
    update_fields=update_fields)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/models/base.py", line 650, in save_base
    result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/models/manager.py", line 215, in _insert
    return insert_query(self.model, objs, fields, **kwargs)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/models/query.py", line 1675, in insert_query
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 943, in execute_sql
    cursor.execute(sql, params)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/backends/util.py", line 41, in execute
    return self.cursor.execute(sql, params)
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/backends/postgresql_psycopg2/base.py", line 56, in execute
    six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
  File "/home/foo_eins_d/local/lib/python2.7/site-packages/django/db/backends/postgresql_psycopg2/base.py", line 54, in execute
    return self.cursor.execute(query, args)
IntegrityError: duplicate key value violates unique constraint "foo_unique_fk_uniq"
DETAIL:  Key (some_unique_key)=(2081, myappTest) already exists.

I have not found the details, yet. I hope to investigate this in the next days. I will post more details, if I found a solution.

I am not sure if this is related to django typed models. Maybe this would happen with multi table inheritance, too.

Magic null=True on subclasses fields is too implicit

No reason callers can't add the null=True themselves. It's confusing that typedmodels is doing it silently.

Backwards incompatible change though. So here's a two-step deprecation plan:

  • in 0.8, fields defined on typed subclasses will be implicitly nullable only if they haven't got a default value. This behaviour will trigger a DeprecationWarning. To get existing behaviour, specify a default value or explicitly use null=True

  • in 0.9, fields defined on typed subclasses won't be implicitly made nullable at all. If no default value is given, and no null=True is given, a FieldError will be raised.

open to discussion... 😄

using db_column to distinguish field with same names in child models

Hi there,

I am just wandering, would it be (theoretically, I see it is not possible right now) possible to use same names for fields in derived classes if each used a different db_column name? At first glance, it seems to me that this should be feasible.

Best regards
Beda

Typedmodel subclass has duplicate fields

Possibly a django 1.4 problem.

E.g.

class Animal(TypeModel):
    owner = models.ForeignKey(Owner, related_name="animals")
    name = models.CharField(max_length=40)

class Cat(Animal):
    def speak(self):
        return "Meow"

class Dog(Animal):
    def speak(self):
        return "Woof"

cat = Cat(owner=owner, name='Fluffy')
cat.save()  # missing owner violates key constraints

But constructing with the superclass works:

cat = Animal(type='app.cat', owner=owner. name='Fluffy')
cat.save()  # worked

Seems to be that the class _meta.fields list contains duplicates, so the first time values are popped from kwargs, then removed on the second pass.

Abstract intermediary class

HI!

I've just read the issue here:

#61

And it seems I am running into the same kind of problem.

I would like to achieve the following structure.

class BaseClass(TypedModel):
    a = models.CharField(...)


class BaseClassWithFlavor(BaseClass):
    class Meta:
        abstract = True

    b = models.CharField(...)



class Child1(BaseClassWithFlavor):
    pass


class Child2(BaseClassWithFlavor):
    pass

Which would result in both Child1 and Child2 having the "b" field.

Is that possible in anyway?

Better docs to abandon prejudice

A thread[1] on django-devel showed, that there are fears of single table inheritance.

  • There is one table which holds all data.
  • STI reduces the database to key-value store
  • All values are nullable

AFAIK there are two tables for this:

class Animal(TypedModel):
    pass
class Canine(Animal):
    pass

class Fruit(TypedModel):
    pass
class Aple(Fruit):
   pass

It is very easy to make one column not nullable: Add a constraint which checks the type column and the column which should be not nullable. It could be implemented in django typed models to implemented this:

Pseudocode for constraint:

if type=="Aple":
    check color is not NULL

Thread on django-devel
https://groups.google.com/d/msg/django-developers/-UOM8HNUnxg/keyHsbGE6WgJ

Child class in different app

There is a central app core which defines a class Plugin and there is an optional app my_plugin which subclasses from Plugin.

I run manage.py schemamigration my_plugin --auto, but Nothing seems to have changed..

I need to run manage.py schemamigration core --auto to get the changes dumped to a migration file.

This does not work, since core does not depend on my_plugin. The plugin should be optional. The relevant schema migrations need to be in the app of my_plugin.

Did you understand my problem?

What can I do?

django 1.7 support

Not working at all with django 1.7

Simple example

class Category(TypedModel):
    slug = models.SlugField(unique=True)
    title = models.CharField(max_length=255)


class Hub(Category):
    pass


class Channel(Category):
    pass

and syncdb error output

Traceback (most recent call last):
File "./manage.py", line 10, in
execute_from_command_line(sys.argv)
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/core/management/init.py", line 427, in execute_from_command_line
utility.execute()
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/core/management/init.py", line 391, in execute
django.setup()
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/init.py", line 21, in setup
apps.populate(settings.INSTALLED_APPS)
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/apps/registry.py", line 106, in populate
app_config.import_models(all_models)
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/apps/config.py", line 190, in import_models
self.models_module = import_module(models_module_name)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/init.py", line 37, in import_module
import(name)
File "/WWW/Python/My/fun/funsite/project/fun/models.py", line 14, in
class Channel(Category):
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/typedmodels/models.py", line 75, in new
Original = super(TypedModelMetaclass, meta).new(meta, base_class.name+'Original', (base_class,), {'Meta': original_meta, 'module': base_class.module})
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/db/models/base.py", line 299, in new
new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
File "/WWW/Python/My/fun/env/lib/python2.7/site-packages/django/apps/registry.py", line 201, in register_model
(model_name, app_label, app_models[model_name], model))
RuntimeError: Conflicting 'categoryoriginal' models in application 'fun': <class 'project.fun.models.CategoryOriginal'> and <class 'project.fun.models.CategoryOriginal'>.

does anybody know simple way to fix this error?

would be great to get it working with django 1.7 together.

1.7 version is awesome also as this package ;-)

TypedModel instance missing `get_type_display` method

Since choices doesn't have a truthy value, django.db.models.fields.Field doesn't add the get_FOO_display (code, docs) which in this case would be get_type_display.

IMO this is a Django bug (I'll file a ticket shortly), however opening an issue here for transparency and tracking (if they reject the ticket).

FYI django_model_utils works around this by adding dummy choices: https://github.com/jazzband/django-model-utils/blob/master/model_utils/fields.py#L75
So there is a (disappointing) workaround

XML serialization doesn’t work.

The reason is that XML serializer class inherits from base.Serializer and not PythonSerializer which we monkey patch. Solution would be to monkey patch start_object method in XML serializer class, however this would mean copy-pasting 17 lines of code, so maybe it would be better to make some adjustments to Django core.

Please document get_type_classes()

Please document get_type_classes(). I guessed that something like this exists, and it was easy to discover in the source. But docs would be better.

Thank you.

recast doesn't work when passing a class and not a string

Python 3.10.7
Django==4.1.5
django-typed-models 0.12.0

recast() does not work when passing a class and not a string.

Here, base will be self.__class__ because typed model subclasses also have the _typedmodels_registry attribute.

Later, this will evaluates to False if the target typ is not the same as self.__class__.

PS: the test here works because BigCat is a Feline. It should be change so that the target model is not a subclass of self.__class__.

Recasting from base isn't pretty

Let's say somehow I get an (unsaved) Animal instance. There isn't a pretty way to recast as a Canine or Feline:

animal = Animal('phoebo')

# This is a no-op thanks to https://github.com/craigds/django-typed-models/blob/master/typedmodels/models.py#L329
animal.recast(Feline)

# This doesn't work either (unless your model defines __str__ to be <app_name>.<model_name>)
animal.type = Feline

# These will work, but isn't ideal
animal.type = 'my_app.Feline'
animal.recast()

# This works too (but only by coincidence, by how recast is implemented)
animal.type = 'nonesense'
animal.recast(Feline)

IMO animal.recast(Feline) should be supported.
(setting the type is smelly, since type is supposed to be a string field)

abstract base class supported?

I tried to use an AbstractBase class which uses TypedModel, but this fails:

class AbstractBase(typedmodels.TypedModel):

    class Meta:
        abstract=True
  File "/home/foo_eins_dtg/src/foo/foo/models/config.py", line 64, in <module>
    class AbstractBase(typedmodels.TypedModel):
  File "/home/foo_eins_dtg/lib/python2.7/site-packages/typedmodels/models.py", line 231, in __new__
    if not cls._default_manager:
AttributeError: type object 'AbstractBase' has no attribute '_default_manager'

Is this supported?

All models which inherit from this AbstractBase should have each its own table.

concrete type does not inherit `unique_together` of parent

If unique_together is defined in Animal's Meta inner class, it should (but does not) propagate to Canine, since Canine (being of the same backend database table) is in fact subject to that constraint as well.

According to the "Meta inheritance" docs, that is currently how Meta inheritance works in the case of abstract classes.

pip install broken

In my installaton, pip install django-typed-models gives the following error message:

ImportError: Could not import settings 'tests.settings' (Is it on sys.path?): No module named tests.settings

Installing the source code from git directly works perfectly (nice work btw).

Am I missing something?

Using: python 2.7 / django 1.5.1 / pip 1.3.1 / setuptools 0.6

thanks.

Child models are used in South migrations for adding M2M fields

Imagine we have following models:

class DomesticAnimal(TypedModel):
    pass


class Cat(DomesticAnimal):
    pass


class Human(models.Model):
    cats_owned = models.ManyToManyField(Cat)

Then generated South migration will look like:

        # […]
        # Adding M2M table for field cats_owned on 'Human'
        m2m_table_name = db.shorten_name(u'animals_human_cats_owned')
        db.create_table(m2m_table_name, (
            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
            ('human', models.ForeignKey(orm[u'animals.human'], null=False)),
            ('cat', models.ForeignKey(orm[u'animals.cat'], null=False))
        ))
        db.create_unique(m2m_table_name, ['human_id', 'cat_id'])

This won’t work, because of the models.ForeignKey(orm[u'animals.cat'] expression, which should be instead models.ForeignKey(orm[u'animals.domesticanimal'].

Broken subclass functionality with "objects"

After commit: 7b50566

By my recommendation we hard-coded "objects" attribute as Django documentation appeared to make this seem necessary.

Example:
class Exhibitored(TypedModel, PublishModel):

Where PublishModel is overriding the objects manager and TypedModel due to our code now overwrites that same manager.

It appears we could have got around this by modifying _default manager on cls.class. Will submit a pull request with the suggested fix.

Using `Meta.indexes` in a superclass breaks proxy models

django-admin makemigrations gives these errors when you add a Meta.indexes = [...] on a base model:

myapp.SpecificThing: (models.E016) 'indexes' refers to field 'permissions' which is not local to model 'SpecificThing'.
	HINT: This issue may be caused by multi-table inheritance.
myapp.OtherThing: (models.E016) 'indexes' refers to field 'permissions' which is not local to model 'OtherThing'.
	HINT: This issue may be caused by multi-table inheritance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.