I know how to update some field of ForeignKey. For example when I want change last_modified field every time if Configuration or SomeOtherImportantClass is changed:
class Configuration(models.Model):
title = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
class SomeOtherImportantClass(models.Model):
conf = models.ForeignKey(Configuration)
important_number = models.IntegerField()
def save(self, *args, **kwargs):
conf.last_modified = timezone.now() # I'm not sure if it is necessary
conf.save()
return super().save(*args, **kwargs)
but in my real situation the Cofiguration model is a ForeignKey for more than 30 other models. In each of them I want to update configuration.last_modified field for every change performed on them or when another model (which has ForeignKey to some model which has ForeignKey do Configuration) is changed. So it looks like that:
class Configuration(models.Model):
title = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
class A(models.Model):
conf = models.ForeignKey(Configuration) # conf.last_modified must be updated on every change on A model object.
class B(models.Model):
conf = models.ForeignKey(Configuration) # same
...
class Z(models.Model):
conf = models.ForeignKey(Configuration) # same
class AA(models.Model):
some_field = models.TextField()
a = models.ForeignKey(A)
...
class ZZ(models.Model)
some_field = models.TextField()
z = models.ForeignKey(Z)
so even if AA object field "some_field" is changed I want to update last_modified Configuration field. Is there any recursion way to declare it once in Configuration or somewhere else?
UPDATE: Great-granchilds like AAA and AAAA classes can exist too.
Use abstract base classes as explained in the docs. For A-Z it's quite easy:
class ConfigurationChild(Model):
conf = ForeignKey(Configuration)
class Meta:
abstract = True
def save(self):
self.conf.last_modified = ...
self.conf.save()
super().save()
class A(ConfigurationChild):
# other fields, without conf
For the grand-children it's a bit more complex because then don't have a reference to conf directly. Set an attribute on the base class that you populate on each child class:
class ConfigurationDescendant(Model):
conf_handle = None
class Meta:
abstract = True
def get_conf(self):
if not self.conf_handle:
return None # or raise an error
parent = getattr(self, self.conf_handle)
if isinstance(parent, ConfigurationDescendant):
return parent.get_conf() # recursion
else:
# reached `ConfigurationChild` class, might want to check this
return parent.conf if parent else None
def save(self):
conf = self.get_conf()
# you might want to handle the case that the attribute is None
if conf:
conf.last_modified = ...
conf.save()
super().save()
class AA(ConfigurationDescendant):
conf_handle = 'a'
a = ForeignKey(A)
class AAA(ConfigurationDescendant):
conf_handle = 'aa'
aa = ForeignKey(AA)
The above code will handle the case when the chain breaks because conf_handle is missing on one of the parents. In which case None is returned and nothing happens. I'm not checking if the handle is set wrongly (i.e. not pointing in the right direction towards the parent Configuration), that will raise an exception which you probably want so you can catch mistakes.
Related
I am running a django app and have a setup like this:
ModelSuper(models.Model):
class Meta:
abstract = False
ModelSub1(ModelA):
name = models.CharField(...)
def __str__:
return self.name
ModelSub2(ModelA)
name = models.CharField(...)
def __str__:
return self.name
ModelForeign(models.Model):
element = models.ForeignKey(ModelA)
def __str__:
return self.name
So ModelForeign has a FK to ModelSuper. What happens now is that when I create an instance of ModelForeign I can choose if it belongs either to ModelSub1 or to ModelSub2. But the string representation is ModelSuper Onject (3) where (3) is the id.
Normally I can change this representation by overwriting the __str__ method on the model, but since I do not have any fields on the Supermodel I can't return anything.
What I tried:
I have already implemented the __str__ method in the Submodels but that does not help.
I wanted to make the Super model abstract. But this does not let me point FKs to the Supermodel, so I can't do this. My setup requires this FK
I used a generic FK with django's ContentType framework. This is also not an option because it messes completely with my app and is also not recommended from an SQL perspective.
Also when I do API-calls I get ModelSuper Onject (3) back instead of a human-readable name.
Is there a way to do what I intend to do? Thanks in advance for help and hints. Very much appreciated!
EDIT1: What I tried thanks to Abdul's help:
class ModelA(models.Model):
class Meta:
abstract = False
TYPE_CHOICES = [('sub1', 'sub1'), ('sub2', 'sub2')]
type_model = models.CharField(max_length=50, choices=TYPE_CHOICES, null=True, blank=True)
def __str__(self):
if self.type_model == "sub1":
return "sub1"
elif self.type_model == "sub2":
return "sub2"
else:
return "unkown"
I am not understanding how your foreign key works as model inheritance means the tables are separate. How about trying something like this:-
ModelA(models.Model):
TYPE_CHOICES = [('Sub1', 'ModelSub1'), ('Sub2', 'ModelSub2')]
model_type = models.CharField(max_length=4, choices=TYPE_CHOICES)
def __str__:
# Return string representation using if-else
class Meta:
abstract = False
ModelSub1(ModelA):
name = models.CharField(...)
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def __str__:
return self.name
ModelSub2(ModelA)
name = models.CharField(...)
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def __str__:
return self.name
ModelForeign(models.Model):
element = models.ForeignKey(ModelA)
def __str__:
return self.name
Prerequisite
I want to implement abstract inheritance:
class Base(models.Model):
title = models.CharField()
slug = models.SlugField()
description = models.TextField()
class Meta:
abstract = True
class ChildA(Base):
foo = models.CharField()
class ChildB(Base):
bar = models.CharField()
For multiple reasons I need a db representation of the hierarchy of these classes. So I want to create a model like this one (left and right attributes allow us to identify instance's place in the node tree):
class Node(models.Model):
app_label = models.CharField()
model_name = models.CharField()
parent = models.ForeignKey('self', blank=True, null=True)
right = models.PositiveIntegerField()
left = models.PositiveIntegerField()
The Problem
I need something similar to this:
class Base(models.Model):
...
def __init_subclass__(cls):
app_label = cls._meta.app_label
model_name = cls._meta.model_name
parent_id = ? # I am not sure how do we get parent's id for now, but it should be manageable
obj = Node.objects.create('app_label'=app_label, 'model_name'=model_name, 'parent'=parent_id)
obj.save()
So, as we subclass an abstract model, a new node is created that represents this new model in the hierarchy tree. Unfortunately, it won't work. It seems __init_subclass__ is invoked before Model class is properly initialized, so cls._meta.model_name will return incorrect value (parent's model_name, in fact). Can we bypass this (or use some other hook)?
Other concerns
I am not sure if this whole idea is sensible. I previously used multi-table inheritance, but at some point SQL queries became really ugly, so I am trying to fix it by using abstract models instead. But we still need to tie models to each other, a node tree seems appealing. This way I get a concrete model to manage multiple tables simultaneously, like this:
class NodeManager(models.Manager):
def get_queryset(self):
root = self._get_root_node()
fields = root._meta.get_fields()
field_names = [field.name for field in fields]
descendants = list(self._get_descendant_models(root))
queryset_list = []
for model in descendants:
qs = model.objects.values(*field_names)
annotated_qs = qs.annotate(
resource_model=models.Value(
root._meta.model_name,
models.CharField(max_length=120)
)
)
queryset_list.append(annotated_qs)
if len(queryset_list) > 1:
merged_queryset = queryset_list[0].union(*queryset_list[1:])
elif len(queryset_list) == 1:
merged_queryset = queryset_list[0]
else:
merged_queryset = None
return merged_queryset
This is not how managers are supposed to be used, I guess, so I am not sure if it's fine.
I don't want to focus on this, it's mainly to give a better idea what I am aiming for. But if you let me know whether you think it's fine or not, I will greatly appreciate it.
Django has ContentType module that can be directly used for this purpose but you would need to do some extra things to get what you need.
You need to handle app ready method that's defined by AppConfig class. The problem is you need to handle the App ready method for each app, instead of adding this code to every app you can just add this as a base class.
class BaseAppConfig(AppConfig):
def add_or_update_node(self, model, super_classes):
# assuming Node model is defined in a separate app called node
from node.models import Node
super_classes = super_classes[::-1]
super_classes.append(model)
prev_node = None
for super_class in super_classes:
node, _ = Node.objects.get_or_create(app_label=super_class._meta.app_label,
model_name=super_class._meta.model_name)
if node.parent is None and prev_node is not None:
node.parent = prev_node
node.save()
prev_node = node
def ready(self):
import inspect
// if variable is not set than do not do anything
if os.getenv('CREATE_NODE') is None:
return
for model_name, model in self.models.items():
super_classes = []
for clazz in inspect.getmro(model):
if clazz == Model or clazz == object or clazz == model:
continue
super_classes.append(clazz)
# no super class
if len(super_classes) == 0:
continue
self.add_or_update_node(model, super_classes)
This handles only the case when you have a single hierarchy model, what about the case when a model is extending from multiple abstract models, left for the op to work upon.
You need to extend this class in your apps.py file.
class CoreConfig(BaseAppConfig):
name = 'Core'
def ready(self):
super(CoreConfig, self).ready()
I'm playing around in Django, and wondering if there is a way to loop through instances of two different models I have created?
/ models.py:
class Tran(models.Model):
name = models.CharField(max_length=300)
description = models.CharField(max_length=2000)
type = models.ForeignKey(TransactionType)
def __str__(self):
return self.name
class DocLink(models.Model):
trann = models.ForeignKey(Transaction)
t_link = models.CharField(max_length=2000)
t_display = models.CharField(max_length=1000)
p_display = models.CharField(max_length=300)
p_link = models.CharField(max_length=2000)
def __str__(self):
return self.link
What I want to do:
Look through each of the Tran instances and create a default value for the links/displays in the DocLink table instead of doing it manually.
Is there anyway I can be pointed in the right direction?
If you want to set links/displays default value in DocLink instance based on trann field you can override model's save method.
For example following code shows how to set t_link if it doesn't have a value:
class DocLink(models.Model):
trann = models.ForeignKey(Transaction)
t_link = models.CharField(max_length=2000)
t_display = models.CharField(max_length=1000)
p_display = models.CharField(max_length=300)
p_link = models.CharField(max_length=2000)
def __str__(self):
return self.link
def save(self, *args, **kwargs):
if not self.t_link:
pass # TODO set self.t_link based on trann
super(DocLink, self).save(*args, **kwargs)
Also you can change model's trann field to:
trann = models.ForeignKey(Transaction, related_name="doclinks")
And then access to all DocLinks of a Tran with:
# t is an instance of Tran class
t.doclinks.all()
So you can loop through this list and do what you want.
edit: I completely rewrote the question as the original one didn't clearly explain my question
I want to run a function which is specific to each particular model instance.
Ideally I want something like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.FunctionField() #stores a function specific to this instance
x = MyModel(data='originalx', perform_unique_action=func_for_x)
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModel(data='originaly', perform_unique_action=func_for_y)
y.perform_unique_action() #will do whatever is specified for instance y
However there is no datatype FunctionField. Normally this would be solvable with inheritance, and creating subclasses of MyModel, maybe like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class MyModelX(MyModel):
perform_unique_action = function_X
class MyModelY(MyModel):
perform_unique_action = function_Y
x = MyModelX(data='originalx')
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModelY(data='originaly')
y.perform_unique_action() #will do whatever is specified for instance y
Unfortunately, I don't think I can use inheritance because I am trying to access the function this way:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel = SecondModel.objects.get(other_data=3)
secondmodel.mymodel.perform_unique_action()
The problem seems to be that I don't know what type the foreign key is going to be in SecondModel if I override the perform_unique_action in subclasses.
Can I access MyModel from SecondModel as a foreign key and still have a unique function for each instance of MyModel?
This works for me. I haven't tested it, but you should be able to create another class and override their methods and it'll work. Check the class Meta line, it'll treat it as an abstract class. Here's an example of my actual classes that I'm working on right now.
EDIT: Added VoteComment class and tested it. It works as expected!
class Vote(models.Model):
VOTE_ENUM = (
(VoteEnum.DOWN_VOTE, VoteEnum.toString(VoteEnum.DOWN_VOTE)),
(VoteEnum.NONE, VoteEnum.toString(VoteEnum.NONE)),
(VoteEnum.UP_VOTE, VoteEnum.toString(VoteEnum.UP_VOTE)),
)
question = models.ForeignKey(Question, null=False, editable=False, blank=False)
voter = models.ForeignKey(User, blank=False, null=False, editable=False)
vote_type = models.SmallIntegerField(default=0, null=False, blank=False, choices=VOTE_ENUM)
class Meta:
abstract = True
def is_upvote(self):
return self.vote_type > 0
def is_downvote(self):
return self.vote_type < 0
class VoteAnswer(Vote):
answer = models.ForeignKey(Answer, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "answer"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, answer.text[:32])
def is_upvote(self):
return "FOO! "+str(super(VoteAnswer, self).is_upvote())
class VoteComment(Vote):
comment = models.ForeignKey(Comment, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "comment"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, comment.text[:32])
def is_upvote(self):
return "BAR!"
I came up with two ways of having a specific function defined for each object. One was using marshal to create bytecode which can be stored in the database (not a good way), and the other was by storing a reference to the function to be run, as suggested by Randall. Here is my solution using a stored reference:
class MyModel(models.Model):
data = models.CharField(max_length=100)
action_module = models.CharField(max_length=100)
action_function = models.CharField(max_length=100)
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel_obj = SecondModel.objects.get(other_data=3)
#The goal is to run a function specific to the instance
#of MyModel referred to in secondmodel_obj
module_name = secondmodel_obj.mymodel.action_module
func_name = secondmodel_obj.mymodel.action_function
module = __import__(module_name)
func = vars(module)[func_name]
func()
Thanks to everyone who replied, I couldn't have got to this answer if it weren't for your help.
You could achive some similar behavior overriding the save method. And providing special callbacks to your instances.
Something like:
def default_function(instance):
#do something with the model instance
class ParentModel(model.Model):
data = models.CharField()
callback_function = default_function
def save(self, *args, **kwargs):
if hasattr(self, 'callback_function'):
self.callback_function(self)
super(ParentModel, self).save(*args, **kwargs)
class ChildModel():
different_data = models.CharField()
callback_function = other_fun_specific_to_this_model
instance = ChildModel()
#Specific function to this particular instance
instance.callback_function = lambda inst: print inst.different_data
instance.save()
You can write endpoints on your server and limit their access to just your self. Then store in each model instance corresponding url. For example:
views.py
def funx_x(request):
pass
def func_y(request):
pass
models.py:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.URLField()
and then:
x = MyModel(data='originalx', perform_unique_action='http://localhost/funx_x')
requests.post(x.perform_unique_action)
i dont know whether i understand u correct or not. but you can check out this example here.
Example:
A string representing an attribute on the model. This behaves almost the same as the callable, but self in this context is the model instance. Here's a full model example:
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')
My models file works just fine. As soon as I replace every models.Model with MyModel (a child-class of models.Model), one of my models raises a
<class 'puppy.cms.models.Appearance'> has more than 1 ForeignKey to <class 'puppy.cms.models.Segment'>
exception. The only thing that I am doing in the child class is override the clean method.
What could I be doing wrong?
class SansHashUrl(object):
""" Upon each call to clean, iterates over every field,
and deletes all '#/' and '#!/' occurances.
IMPORTANT: This mixin must be listed first in the inheritance list to work
properly. """
def clean(self):
attrs = (field.attname for field in self.__class__._meta.fields
if isinstance(field, models.CharField)
or isinstance(field, models.TextField))
for attr in attrs:
attr_value = self.__getattribute__(attr)
tokens = attr_value.split()
for i, token in enumerate(tokens):
if has_internal_domain(token):
suggested_url = re.sub('#!?/','', token)
tokens[i] = suggested_url
self.__setattr__(attr, ' '.join(tokens))
class MyModel(SansHashUrl, models.Model):
pass
Model that throws the error:
class Appearance(MyModel):
appearance_type = models.CharField(max_length=20,
choices=APPEARANCE_TYPE_CHOICES)
person = models.ForeignKey(Person, related_name='person_appearance')
item = models.ForeignKey(ManagedItem)
class Meta:
unique_together = (('person', 'item'),)
def __unicode__(self):
return self.person.__unicode__()
In reference to:
class Segment(Story, HasStatsTags, HasFullUrl):
...
It might be useful to note that Story is a subclass of ManagedItem (a subclass of MyModel).
You need to declare MyModel (and probably ManagedItem) as an abstract model in its Meta class, otherwise Django will create a separate table for them and define FKs between them.
class MyModel(SansHashUrl, models.Model):
class Meta:
abstract = True