I am working on some Django project (first time) and after lot of searching i have no clue how to proper update object attribute after creation. I have sucha models.py
from django.db import models
import os
# Create your models here.
class Place(models.Model):
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Add templates folder for dynamic usage, os independent
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates/places")
name = models.CharField(max_length=100, unique=True)
display_name = models.CharField(max_length=100)
floor = models.DecimalField(max_digits=3, decimal_places=0)
template = models.FilePathField(path=TEMPLATE_DIR, match=".*html")
url = models.URLField(unique=True)
# Display name of object in object list view
def __str__(self):
return self.name
Question is how to update url attribute after object creation of url to this particular object, it should be something like (base url + object_id i know object_id is created after object creation) but after searching documentation i dont have ideas how to do this properly.
I tried get_absolute_path but with no success.
Maybe some kind post_save method override ?
One option would be to override the model save method and detect if the model instance is being added or updated and update the url field accordingly:
class Place(models.Model):
# ...
def save(self, *args, **kwargs):
is_adding = self._state.adding
super(Place, self).save(self, *args, **kwargs)
if is_adding:
url = os.path.join(self.TEMPLATE_DIR, str(self.pk))
super(Place, self).save(self, *args, **kwargs)
However, you needn't actually store the url as you can derive the value from other model fields when required. Hence, you can delete the url field and create a url method instead:
class Place(models.Model):
# ...
#property
def url(self):
return os.path.join(self.TEMPLATE_DIR, str(self.pk))
Here, the #property decoration here allows you to access place.url as if it was a model field.
This might be a good time to introduce something like Django celery into you're project and run this task async after creation.
You could have a task in a file like this:
# I'm making assumptions on project architecture..
from app.places import Place
from app.places.utils import PLACE_BASE_URL
#shared_task
def task_update_place_url(place_id):
"""
Simple method to update Place url after creation.
"""
place = Place.objects.get(pk=place_id)
place.url = PLACE_BASE_URL + place_id
place.save()
Then inside of your view when you create your Place you can do this:
import json
from django.http.response import HttpResponse
from app.places.models import Place
from app.places.tasks import task_update_place_url
def create_place(request):
"""
Creates a new Place and update url in task
"""
data = json.loads(request)
place = Place.objects.create(**data)
task_update_place_url.delay(place.id)
return HttpResponse("Finished")
Related
I have a function make_fields_permissions that I need to use it inside the model calss in order to parse the fields and to make permissions for each field like [('can_view_first_name_field','can view first name'),...]
goal I need to call and override Person class and inside it self
I tried
def __init__(self,*args, **kwargs):
self.Meta.permissions = make_fields_permissions(self.model)
super().__init__(*args, **kwargs)
My code look like this
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def __init__(self, *args, **kwargs):
# kwargs['user']['permissions'] = make_fields_permissions(Profile)
# x = self['age']
super().__init__(*args, **kwargs)
class Meta:
permissions = make_fields_permissions(Person) #< I can't use the same model inside meta
Your goal is as follows:
Goal X (Real goal): Create permissions dynamically according to the model fields
Goal Y (Perceived goal that will achieve X): Call the model class while creating it.
Note: See What is the XY problem?
Let us first discuss Goal Y and why it is too complex, and somewhat unfeasable. When one wants to customize how the creation of a class occurs one would use metaclasses, and at first sight this would appear as a perfect solution for your needs (in fact if you do create one properly it would be). But the problem here is that Model already has a metaclass being ModelBase and it is already doing lots of stuff and is a little complicated. If we would want a custom metaclass we would need to inherit from it and very carefully work around its implementation to do what we want. Furthermore making it would not be the end of the story, because then we would need to maintain it since it would be easily breakable by updates to Django. Hence Goal Y is not feasible.
Moving on to the actual Goal X to do that one can Programmatically create permissions [Django docs]. A good place to do this would be in the app configs ready method. For all apps created using startapp there is an apps.py file which has an appconfig inheriting from AppConfig, when the models are loaded its ready method is called. Hence this method is used to do various tasks like attaching signals, various setup like tasks, etc. Modify the appconfig of your app to create permissions programmatically like so:
from django.apps import AppConfig
class YourAppConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField' # Don't modify, keep it as it is in your code
name = 'your_app' # Don't modify, keep it as it is in your code
def ready(self):
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from path.to import make_fields_permissions
from .models import Person
# import models here, not outside as models may not be loaded yet
content_type = ContentType.objects.get_for_model(Person)
for codename, name in make_fields_permissions(Person):
Permission.objects.get_or_create(
codename=codename, # 'can_view_first_name_field'
name=name, # 'can view first name'
content_type=content_type,
)
I would like to check the value of a field (lightStatusA) when a new record is saved into my Django database. I feel like iv'e read the docs 10 times and still can't grasp how to get this. Here is my current models.py code:
from django.db import models
from accounts.models import Customer
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save
class Data(models.Model):
author = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,)
tempData= models.CharField(max_length=50,blank=True,)
humidData= models.CharField(max_length=50,blank=True,)
lightStatusA= models.BooleanField(default=True,)
dateTime = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.author)
def check_light_status(sender, **kwargs):
if kwargs['created']: #make sure its a new record
#need logic to grab instance.lightStatusA and check it's value
post_save.connect(check_light_status, sender = Data)
Is there some way of passing this value as an argument to the signal?
The check_light_status function can accept an instance parameter.
From the docs:
instance
The actual instance being saved.
Update: You said this:
instance returns the author of the post.
I am going to use my powers of deduction to guess that you tried print(instance) and saw the author. Look at your __str__ implementation.
def __str__(self):
return str(self.author)
I'd say you sabotaged yourself a bit there ;)
Ahh ok got it:
def check_light_status(sender, instance, **kwargs):
if kwargs['created']: #make sure its a new record
print(instance.lightStatusA)
This prints the the field I need to run some logic against.
I wrote this code that dynamically generates url patterns from the database. These urls have only one level path: domain.com/something.
someapp/models.py
class SomeModel(models.Model):
pattern = models.CharField(max_length=50)
name = models.CharField(max_length=50)
text = models.CharField(max_length=50)
someapp/apps.py
class SomeAppConfig(AppConfig):
name = 'someapp'
def ready(self):
from .models import SomeModel
from .urls import urlpatterns
from . import views
urls_in_db = SomeModel.objects.all()
for url_in_db in urls_in_db:
urlpatterns.append(path(url_in_db.pattern,
views.SpecialView.as_view(),
name=url_in_db.name)
someapp/views.py
class SpecialView(generic.TemplateView):
template_name = 'template/view_template.html'
model = SomeModel
def get_context_data(self, **kwargs):
context = super(SpecialView, self).get_context_data(**kwargs)
context['content'] = SomeModel.objects.get(pattern=self.request.path)
return context
Is this solution an anti-pattern? And, if so, why?
Thanks
Yes, your solution is an anti-pattern. Django supports parameters in URL patterns that get captured and become available in the corresponding view. Using these URL parameters, you can write and maintain a single URL pattern for every record of a certain type in your database.
Take a look at this example of URL parameters.
Finally, also note how your solution could potentially have very poor performance since you are potentially creating millions of URL patterns depending on the size of your database.
I'm trying to add a plugin to a PlaceholderField from code.
I have a model (Question) with a few fields, one of them is a PlaceholderField.
What I want to do is adding a TextPugin (or any other generic cms_plugin) to that Placeholder Field. This is needed as I don't want people to add the TextPlugin manually from the frontend edit mode of the cms, but rather creating it myself so they can just add the right content after.
I know there's add_plugin from cms.api, but still I'd need to figure out a way to convert the PlaceholderField to Placeholder for it to work.
This is the code I have right now.
models.py
from django.utils.translation import ugettext as _
from django.db import models
from djangocms_text_ckeditor.cms_plugins import TextPlugin
from cms.models.fields import PlaceholderField
from cms.api import add_plugin
class Question(models.Model):
topic = models.ForeignKey('Topic')
question = models.CharField(_("Question"),max_length=256)
answer = PlaceholderField ('Answer plugin')
priorityOrder = models.IntegerField(_("Priority Order"))
def save(self, *args, **kwargs):
# Here's the critical point: I can cast self.answer to PlaceholderField,
# but I can't cast it to a Placeholder or add a placeholder to it
add_plugin( ????, plugin_type='TextPlugin', language='us',)
super(Question, self).save(*args, **kwargs)
# set the correct name of a django.model object in the admin site
def __unicode__(self):
return self.question
class Topic(models.Model):
title = models.CharField(_("Topic title"),max_length=256)
priorityOrder = models.IntegerField(_("Priority Order"))
# set the correct name of a django.model object in the admin site
def __unicode__(self):
return self.title
Any help (including alternative ways of doing this) is really welcome!
A PlaceholderField is nothing but a ForeignKey that auto-creates the relation to a new Placeholder object when a new instance is created.
As a result, you cannot use add_plugin on a PlaceholderField on an unsaved instance. You need to call super().save() first, then call add_plugin(self.answer, ...).
I wrote an app that is mainly allowing the user to drag tags to objects via jQuery. I want to allow that app to work for multiple models, so that i can tag ie. a user or an image. For this i thought about adding a class containing a "dropcode" to each models representation on the page:
<div class="droppable" dropcode="drop_img"> some image </div>
<div class="droppable" dropcode="drop_user"> some user </div>
I would like to specify the "dropcode" for each of the models in the main projects settings:
droppable_models={User:'drop_user',Image:'drop_img'}
After installing the app, i want to be able to retrieve the dropcode from each instance of the affected models:
image_instance1.dropcode -> drop_img
image_instance2.dropcode -> drop_img
user_instance1.dropcode -> drop_user
user_instance2.dropcode -> drop_user
That way i could just simply use the dropcode on the page, return it via jQuery to select the right model
Is that possible? Is there a better way to achieve what i want do do?
Why not add a dropcode property to the appropriate models? eg.
class Image(models.Model):
....
dropcode = property(lambda self: "drop_img")
For existing models where you can't edit the models.py (such as User model), add code like this to the models.py of one of your own apps:
from django.contrib.auth.models import User
class UserMixin:
dropcode = property(lambda self: "drop_user")
User.__bases__ += (UserMixin,)
Then in your template, use an if tag to check whether an item has a dropcode. You can therefore eliminate the droppable_models setting:
<div class="droppable"{% if item.dropcode %} dropcode="{{item.dropcode}}"{% endif %}>{{item}}</div>
If your application should work with any model, then you should use the contentypes framework:
Django includes a contenttypes application that can track all of the
models installed in your Django-powered project, providing a
high-level, generic interface for working with your models.
Implementing this allows your application to be generic - it can work with any installed model.
EDIT:
Here is how to use content types framework (directly from the documentation):
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.tag
Now, to add a tag to the item:
item = Model.object.get(pk=1)
ti = TaggedItem(tag='foo',content_object=item)
ti.save()
To get tags for a particular item:
i = Image.object.get(pk=1) # or any instance of the Image object, or any object
the_type_of_object = ContentType.objects.get_for_model(i)
# Find all tags for this object
image_tags = TaggedItem.objects.filter(content_type__pk=the_type_of_object.id,
object_id=i.id)
Based on the tips of Simon and Burhan i came to the following solution: I define the affected models in the settings and then add the DragToTagable Class as Base Class to those models. This look like that in the settings:
DROPPABLE_MODELS=('test.TestItem:item',)
Thats all that needs to be done to apply the apps functionality to that model, or any other model of my project. The model.py of my app looks like this now:
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
try:
#perform this when starting the project
for definition in settings.DROPPABLE_MODELS:
#parse contenttype
parsed=definition.split(':')
dropcode=parsed[1]
parsed=parsed[0].split('.')
appname=parsed[0]
modelname=parsed[1]
#get the models class for the specified contenttype
model_class=ContentType(app_label=appname, model=modelname).model_class()
#create class Mixin, containing the dropcode property
class DragToTagable:
dropcode = dropcode
#add DragToTagable as a base class to the model class
model_class.__bases__+=(DragToTagable,)
except AttributeError:
pass
except:
import sys
print "Unexpected error:", sys.exc_info()[0]
raise
This way i do not have to create an additional table, like in burhans proposal. And the app stays completely independent and requires no work on existing models to be implemented.
Thanks for the tips.