Django model inheritance: create sub-instance of existing instance (downcast)? - python

I'm trying to integrate a 3rd party Django app that made the unfortunate decision to inherit from django.contrib.auth.models.User, which is a big no-no for pluggable apps. Quoting Malcolm Tredinnick:
More importantly, though, just as in Python you cannot "downcast" with
Django's model inheritance. That is, if you've already created the User
instance, you cannot, without poking about under the covers, make that
instance correspond to a subclass instance that you haven't created yet.
Well, I'm in the situation where I need to integrate this 3rd party app with my existing user instances. So, if hypothetically I am indeed willing to poke about under the covers, what are my options? I know that this doesn't work:
extended_user = ExtendedUser(user_ptr_id=auth_user.pk)
extended_user.save()
There's no exception, but it breaks all kinds of stuff, starting with overwriting all the columns from django.contrib.auth.models.User with empty strings...

This should work:
extended_user = ExtendedUser(user_ptr_id=auth_user.pk)
extended_user.__dict__.update(auth_user.__dict__)
extended_user.save()
Here you're basically just copying over the values from the auth_user version into the extended_user one, and re-saving it. Not very elegant, but it works.

I found this answer by asking on django-user mailing list:
https://groups.google.com/d/msg/django-users/02t83cuEbeg/JnPkriW-omQJ
This isn't part of the public API but you could rely on how Django loads fixture internally.
parent = Restaurant.objects.get(name__iexact="Bob's Place").parent
bar = Bar(parent=parent, happy_hour=True)
bar.save_base(raw=True)
Keep in mind that this could break with any new version of Django.

If you don't like __dict__.update solution you can do this:
for field in parent_obj._meta.fields
setattr(child_obj, field.attname, getattr(parent_obj, field.attname))

I am using Django 1.6, and my ExtendedUser model is from OSQA (forum.models.user.User). For some bizarre reason the above solutions with dict.__update__ and with setattr sometimes fail. This may have to do with some other models that I have, that are putting constrains on the user tables. Here are two more workarounds that you can try:
Workaround #1:
extended_user = ExtendedUser(user_ptr_id = user.pk)
extended_user.save() # save first time
extended_user.__dict__.update(user.__dict__)
extended_user.save() # save second time
Workaround #2:
extended_user = ExtendedUser(user_ptr_id = user.pk)
extended_user.__dict__.update(user.__dict__)
extended_user.id=None
extended_user.save()
That is, sometimes saving the new child instance fails if you set both pk and id, but you can set just pk, save it, and then everything seems to work fine.

There is an open bug for this very question:
https://code.djangoproject.com/ticket/7623
The proposed patch (https://github.com/django/django/compare/master...ar45:child_object_from_parent_model) is not using obj.__dict__
but creates an dictionary with all field values cycling over all fields.
Here a simplified function:
def create_child_from_parent_model(child_cls, parent_obj, init_values: dict):
attrs = {}
for field in parent_obj._meta._get_fields(reverse=False, include_parents=True):
if field.attname not in attrs:
attrs[field.attname] = getattr(parent_obj, field.attname)
attrs[child_cls._meta.parents[parent_obj.__class__].name] = parent_obj
attrs.update(init_values)
print(attrs)
return child_cls(**attrs)
create_child_from_parent_model(ExtendedUser, auth_user, {})
This method has the advantage that methods that are overwritten by the child are not replaced by the original parent methods.
For me using the original answers obj.__dict__.update() led to exceptions as I was using the FieldTracker from model_utils in the parent class.

What about something like this:
from django.forms.models import model_to_dict
auth_user_dict = model_to_dict(auth_user)
extended_user = ExtendedUser.objects.create(user_ptr=auth_user, **auth_user_dict)

#guetti's answer worked for me with little update => The key was parent_ptr
parent_object = parent_model.objects.get(pk=parent_id)
new_child_object_with_existing_parent = Child(parent_ptr=parent, child_filed1='Nothing')
new_child_object_with_existing_parent.save()
I wanted to create entry in my profile model for existing user, my model was like
from django.contrib.auth.models import User as user_model
class Profile(user_model):
bio = models.CharField(maxlength=1000)
another_filed = models.CharField(maxlength=1000, null=True, blank=True)
At some place I needed to create profile if not exists for existing user so I did it like following,
The example that worked for me
from meetings.user import Profile
from django.contrib.auth.models import User as user_model
user_object = user_model.objects.get(pk=3)
profile_object = Profile(user_ptr=user_object, bio='some')
profile_object.save()

Related

Custom the `on_delete` param function in Django model fields

I have a IPv4Manage model, in it I have a vlanedipv4network field:
class IPv4Manage(models.Model):
...
vlanedipv4network = models.ForeignKey(
to=VlanedIPv4Network, related_name="ipv4s", on_delete=models.xxx, null=True)
As we know, on the on_delete param, we general fill the models.xxx, such as models.CASCADE.
Is it possible to custom a function, to fill there? I want to do other logic things there.
The choices for on_delete can be found in django/db/models/deletion.py
For example, models.SET_NULL is implemented as:
def SET_NULL(collector, field, sub_objs, using):
collector.add_field_update(field, None, sub_objs)
And models.CASCADE (which is slightly more complicated) is implemented as:
def CASCADE(collector, field, sub_objs, using):
collector.collect(sub_objs, source=field.remote_field.model,
source_attr=field.name, nullable=field.null)
if field.null and not connections[using].features.can_defer_constraint_checks:
collector.add_field_update(field, None, sub_objs)
So, if you figure out what those arguments are then you should be able to define your own function to pass to the on_delete argument for model fields. collector is most likely an instance of Collector (defined in the same file, not sure what it's for exactly), field is most likely the model field being deleted, sub_objs is likely instances that relate to the object by that field, and using denotes the database being used.
There are alternatives for custom logic for deletions too, incase overriding the on_delete may be a bit overkill for you.
The post_delete and pre_delete allows you define some custom logic to run before or after an instance is deleted.
from django.db.models.signals import post_save
def delete_ipv4manage(sender, instance, using):
print('{instance} was deleted'.format(instance=str(instance)))
post_delete.connect(delete_ipv4manage, sender=IPv4Manage)
And lastly you can override the delete() method of the Model/Queryset, however be aware of caveats with bulk deletes using this method:
Overridden model methods are not called on bulk operations
Note that the delete() method for an object is not necessarily called when deleting objects in bulk using a QuerySet or as a result of a cascading delete. To ensure customized delete logic gets executed, you can use pre_delete and/or post_delete signals.
Another useful solution is to use the models.SET() where you can pass a function (deleted_guest in the example below)
guest = models.ForeignKey('Guest', on_delete=models.SET(deleted_guest))
and the function deleted_guest is
DELETED_GUEST_EMAIL = 'deleted-guest#introtravel.com'
def deleted_guest():
""" used for setting the guest field of a booking when guest is deleted """
from intro.models import Guest
from django.conf import settings
deleted_guest, created = Guest.objects.get_or_create(
first_name='Deleted',
last_name='Guest',
country=settings.COUNTRIES_FIRST[0],
email=DELETED_GUEST_EMAIL,
gender='M')
return deleted_guest
You can't send any parameters and you have to be careful with circular imports. In my case I am just setting a filler record, so the parent model has a predefined guest to represent one that has been deleted. With the new GDPR rules we gotta be able to delete guest information.
CASCADE and PROTECT etc are in fact functions, so you should be able to inject your own logic there. However, it will take a certain amount of inspection of the code to figure out exactly how to get the effect you're looking for.
Depending what you want to do it might be relatively easy, for example the PROTECT function just raises an exception:
def PROTECT(collector, field, sub_objs, using):
raise ProtectedError(
"Cannot delete some instances of model '%s' because they are "
"referenced through a protected foreign key: '%s.%s'" % (
field.remote_field.model.__name__, sub_objs[0].__class__.__name__, field.name
),
sub_objs
)
However if you want something more complex you'd have to understand what the collector is doing, which is certainly discoverable.
See the source for django.db.models.deletion to get started.
There is nothing stopping you from adding your own logic. However, you need to consider multiple factors including compatibility with the database that you are using.
For most use cases, the out of the box logic is good enough if your database design is correct. Please check out your available options here https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey.on_delete.

Django 'likes' - ManyToManyField vs new model

I'm implementing likes on profiles for my website and I'm not sure which would be the best practice, a ManyToManyField like so:
class MyUser(AbstractBaseUser):
...
likes = models.ManyToManyField('self', symmetrical = False, null = True)
...
or just creating a class Like, like so:
class Like(models.Model):
liker = models.ForeignKey(MyUser, related_name='liker')
liked = models.ForeignKey(MyUser, related_name='liked')
Is one of them a better choice than the other? If so, why?
thanks
The first option should be preffered. If you need some additional fields to describe the likes, you can still use through="Likes" in your ManyToManyField and define the model Likes.
Manipulating the data entries would be also somewhat more pythonic:
# returns an object collection
likes_for_me = MyUser.objects.filter(pk=1).likes
instead of:
me = MyUser.objects.filter(pk=1)
likes_for_me = Like.objects.filter(liked=me)
The second option is basically what is done internally: a new table is created, which is used to create the links between the entities.
For the first option, you let django do the job for you.
The choice is certainly more about how you want to do the requests. On the second options, you would have to query the Like models that match you model, while on the first one, you only have to request the MyUser, from which you can access the connections.
Second option is more flexible and extensible. For example, you'll probably want to track when like was created (just add Like.date_created field). Also you'll probably want to send notification to content author when content was liked. But at first like only (add Like.cancelled boolead field and wrap it with some logic...).
So I'll go with separate model.
I think the one you choose totally depends on the one you find easier to implement or better. I tend to always use the first approach, as it is more straightforward and logical, at least to me. I also disagree with Igor on that it's not flexible and extensible, you can also initiate notifications when it happens. If you are going to use the Django rest framework, then I totally suggest using the first method, as the second could be a pain.
class Post(models.Model):
like = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='post_like')
Then in your view, you just do this.
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def like(request, id):
signed_in = request.user
post = Post.objects.get(id=id)
if signed_in and post:
post.like.add(signed_in)
# For unlike, remove instead of add
return Response("Successful")
else:
return Response("Unsuccessful", status.HTTP_404_NOT_FOUND)
Then you can use the response however you like on the front end.

Django ForeignKey which does not require referential integrity?

I'd like to set up a ForeignKey field in a django model which points to another table some of the time. But I want it to be okay to insert an id into this field which refers to an entry in the other table which might not be there. So if the row exists in the other table, I'd like to get all the benefits of the ForeignKey relationship. But if not, I'd like this treated as just a number.
Is this possible? Is this what Generic relations are for?
This question was asked a long time ago, but for newcomers there is now a built in way to handle this by setting db_constraint=False on your ForeignKey:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.db_constraint
customer = models.ForeignKey('Customer', db_constraint=False)
or if you want to to be nullable as well as not enforcing referential integrity:
customer = models.ForeignKey('Customer', null=True, blank=True, db_constraint=False)
We use this in cases where we cannot guarantee that the relations will get created in the right order.
EDIT: update link
I'm new to Django, so I don't now if it provides what you want out-of-the-box. I thought of something like this:
from django.db import models
class YourModel(models.Model):
my_fk = models.PositiveIntegerField()
def set_fk_obj(self, obj):
my_fk = obj.id
def get_fk_obj(self):
if my_fk == None:
return None
try:
obj = YourFkModel.objects.get(pk = self.my_fk)
return obj
except YourFkModel.DoesNotExist:
return None
I don't know if you use the contrib admin app. Using PositiveIntegerField instead of ForeignKey the field would be rendered with a text field on the admin site.
This is probably as simple as declaring a ForeignKey and creating the column without actually declaring it as a FOREIGN KEY. That way, you'll get o.obj_id, o.obj will work if the object exists, and--I think--raise an exception if you try to load an object that doesn't actually exist (probably DoesNotExist).
However, I don't think there's any way to make syncdb do this for you. I found syncdb to be limiting to the point of being useless, so I bypass it entirely and create the schema with my own code. You can use syncdb to create the database, then alter the table directly, eg. ALTER TABLE tablename DROP CONSTRAINT fk_constraint_name.
You also inherently lose ON DELETE CASCADE and all referential integrity checking, of course.
To do the solution by #Glenn Maynard via South, generate an empty South migration:
python manage.py schemamigration myapp name_of_migration --empty
Edit the migration file then run it:
def forwards(self, orm):
db.delete_foreign_key('table_name', 'field_name')
def backwards(self, orm):
sql = db.foreign_key_sql('table_name', 'field_name', 'foreign_table_name', 'foreign_field_name')
db.execute(sql)
Source article
(Note: It might help if you explain why you want this. There might be a better way to approach the underlying problem.)
Is this possible?
Not with ForeignKey alone, because you're overloading the column values with two different meanings, without a reliable way of distinguishing them. (For example, what would happen if a new entry in the target table is created with a primary key matching old entries in the referencing table? What would happen to these old referencing entries when the new target entry is deleted?)
The usual ad hoc solution to this problem is to define a "type" or "tag" column alongside the foreign key, to distinguish the different meanings (but see below).
Is this what Generic relations are for?
Yes, partly.
GenericForeignKey is just a Django convenience helper for the pattern above; it pairs a foreign key with a type tag that identifies which table/model it refers to (using the model's associated ContentType; see contenttypes)
Example:
class Foo(models.Model):
other_type = models.ForeignKey('contenttypes.ContentType', null=True)
other_id = models.PositiveIntegerField()
# Optional accessor, not a stored column
other = generic.GenericForeignKey('other_type', 'other_id')
This will allow you use other like a ForeignKey, to refer to instances of your other model. (In the background, GenericForeignKey gets and sets other_type and other_id for you.)
To represent a number that isn't a reference, you would set other_type to None, and just use other_id directly. In this case, trying to access other will always return None, instead of raising DoesNotExist (or returning an unintended object, due to id collision).
tablename= columnname.ForeignKey('table', null=True, blank=True, db_constraint=False)
use this in your program

How should I build this Django model to do what I want

This is what I had before (but realized that you can't obviously do it in this order:
class MasterAdmin(models.Model):
"""
A permanent admin (one per Account) that shouldn't be deleted.
"""
admin = models.OneToOneField(AccountAdmin)
class Account(models.Model):
"""
A top-level account in the system.
"""
masteradmin = models.OneToOneField(MasterAdmin)
class AccountAdmin(models.Model):
"""
An Account admin that can be deleted. This includes limited permissions.
"""
account = models.ForeignKey(Account)
I think you can see what I want to do from the example. I want to have an MasterAccountAdmin which shares the attributes from AccountAdmin. The purpose is that I want to give people the ability to delete an AccountAdmin, but not MasterAccountAdmin. I didn't want to just have an attribute on AccountAdmin called "master = models.BooleanField()".
Obviously this example won't work because MasterAdmin is referencing AccountAdmin before its creation, but I wanted to show what I'm trying to achieve. Am I thinking of this all wrong?
Why not just make is_master a property of AccountAdmin and then override the delete() method to ensure is_master is not true?
When you have forward references, use the quotes.
admin = models.OneToOneField('AccountAdmin')
See the docs.
If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself...

How to work with unsaved many-to-many relations in django?

I have a couple of models in django which are connected many-to-many. I want to create instances of these models in memory, present them to the user (via custom method-calls inside the view-templates) and if the user is satisfied, save them to the database.
However, if I try to do anything on the model-instances (call rendering methods, e.g.), I get an error message that says that I have to save the instances first. The documentation says that this is because the models are in a many-to-many relationship.
How do I present objects to the user and allowing him/her to save or discard them without cluttering my database?
(I guess I could turn off transactions-handling and do them myself throughout the whole project, but this sounds like a potentially error-prone measure...)
Thx!
I would add a field which indicates whether the objects are "draft" or "live". That way they are persisted across requests, sessions, etc. and django stops complaining.
You can then filter your objects to only show "live" objects in public views and only show "draft" objects to the user that created them. This can also be extended to allow "archived" objects (or any other state that makes sense).
I think that using django forms may be the answer, as outlined in this documentation (search for m2m...).
Edited to add some explanation for other people who might have the same problem:
say you have a model like this:
from django.db import models
from django.forms import ModelForm
class Foo(models.Model):
name = models.CharField(max_length = 30)
class Bar(models.Model):
foos = models.ManyToManyField(Foo)
def __unicode__(self):
return " ".join([x.name for x in foos])
then you cannot call unicode() on an unsaved Bar object. If you do want to print things out before they will be saved, you have to do this:
class BarForm(ModelForm):
class Meta:
model = Bar
def example():
f1 = Foo(name = 'sue')
f1.save()
f2 = foo(name = 'wendy')
f2.save()
bf = BarForm({'foos' : [f1.id, f2.id]})
b = bf.save(commit = false)
# unfortunately, unicode(b) doesn't work before it is saved properly,
# so we need to do it this way:
if(not bf.is_valid()):
print bf.errors
else:
for (key, value) in bf.cleaned_data.items():
print key + " => " + str(value)
So, in this case, you have to have saved Foo objects (which you might validate before saving those, using their own form), and before saving the models with many to many keys, you can validate those as well. All without the need to save data too early and mess up the database or dealing with transactions...
Very late answer, but wagtail's team has made a separate Django extension called django-modelcluster. It's what powers their CMS's draft previews.
It allows you to do something like this (from their README):
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey
class Band(ClusterableModel):
name = models.CharField(max_length=255)
class BandMember(models.Model):
band = ParentalKey('Band', related_name='members')
name = models.CharField(max_length=255)
Then the models can be used like so:
beatles = Band(name='The Beatles')
beatles.members = [
BandMember(name='John Lennon'),
BandMember(name='Paul McCartney'),
]
Here, ParentalKey is the replacement for Django's ForeignKey. Similarly, they have ParentalManyToManyField to replace Django's ManyToManyField.

Categories