Django cms accessing extended property - django

I've extended the Django cms Page model into ExtendedPage model and added page_image to it.
How can I now acces the page_image property in a template.
I'd like to access the page_image property for every child object in a navigation menu... creating a menu with images...
I've extended the admin and I have the field available for editing (adding the picture)
from django.db import models
from django.utils.translation import ugettext_lazy as _
from cms.models.pagemodel import Page
from django.conf import settings
class ExtendedPage(models.Model):
page = models.OneToOneField(Page, unique=True, verbose_name=_("Page"), editable=False, related_name='extended_fields')
page_image = models.ImageField(upload_to=settings.MEDIA_ROOT, verbose_name=_("Page image"), blank=True)
Thank you!
BR

request.current_page.extended_fields.page_image
should work if you are using < 2.4. In 2.4 they introduced a new two page system (published/draft) so you might need
request.current_page.publisher_draft.extended_fields.page_image
I usually write some middleware or a template processor to handle this instead of doing it repetitively in the template. Something like:
class PageOptions(object):
def process_request(self, request):
request.options = dict()
if not request.options and request.current_page:
extended_fields = None
try:
extended_fields = request.current_page.extended_fields
except:
try:
custom_settings = request.current_page.publisher_draft.extended_fields
except:
pass
if extended_fields:
for field in extended_fields._meta.fields:
request.options[field.name] = getattr(extended_fields, field.name)
return None
will allow you to simply do {{ request.options.page_image }}

Related

flask-admin different view for multiple databases

I have two different databases. They are: stock and commodity. Each database has two tables as follows:
Stock: User_trade_stock, stock_prices
Commodity: User_trade_commodity, commodity_prices
I try to build a web app to handle two databases with flask. When I apply flask-admin to them as follows
admin.add_view(UserView(User_trade_stock, db.session))
admin.add_view(UserView(User_trade_commodity, db.session))
I gives the following eror:
Assertion Error: A name collision occurred between blueprints. Blueprints that are created on the fly need unique name.
I tried to add the bind to the db.session as follows
admin.add_view(UserView(User_trade_stock, db.session(bind='stock_bind')))
admin.add_view(UserView(User_trade_commodity, db.session='commodity_bind')))
I got the following error:
scoped session is already present; no new arguments may be specified
Any helps would be appreciated
There are a couple of issues.
Flask-Admin uses a view's lower cased class name for the automatically generated Blueprint name. As you are using UserView twice you have a Blueprint name collision. To overcome this you can specify an endpoint name when you instance a view, for example:
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(TestView(StockTest, db.session, category='Stock', name='Test', endpoint='stock-test'))
admin.add_view(TestView(CommodityTest, db.session, category='Commodity', name='Test', endpoint='commodity-test'))
and to get the urls of the views you would use the following code:
url_for('stock-test.index')
url_for('stock-test.edit')
url_for('commodity-test.index')
url_for('commodity-test.edit')
Secondly, if you want to use the bind feature of Flask-Sqlalchemy you should use the __bind_key__ attribute on the table model, for example:
class User(db.Model):
__bind_key__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
Here is a single file example illustrating both concepts. You will need to run the flask commands flask create-databases and flask populate-databases before you use the app itself. Note I've used a mixin class, TestMixin, to define the model columns.
import click
from flask import Flask
from flask.cli import with_appcontext
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from faker import Faker
from sqlalchemy import Integer, Column, Text
db = SQLAlchemy()
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_BINDS'] = {
'stock': 'sqlite:///stock.db',
'commodity': 'sqlite:///commodity.db'
}
db.init_app(app)
class TestMixin(object):
id = Column(Integer, primary_key=True)
name = Column(Text(), unique=True, nullable=False)
class StockTest(db.Model, TestMixin):
__bind_key__ = 'stock'
class CommodityTest(db.Model, TestMixin):
__bind_key__ = 'commodity'
#click.command('create-databases')
#with_appcontext
def create_databases():
db.drop_all(bind=['stock', 'commodity'])
db.create_all(bind=['stock', 'commodity'])
#click.command('populate-databases')
#with_appcontext
def populate_databases():
_faker = Faker()
db.session.bulk_insert_mappings(StockTest, [{'name': _faker.name()} for _ in range(100)])
db.session.bulk_insert_mappings(CommodityTest, [{'name': _faker.name()} for _ in range(100)])
db.session.commit()
class TestView(ModelView):
pass
app.cli.add_command(create_databases)
app.cli.add_command(populate_databases)
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(TestView(StockTest, db.session, category='Stock', name='Test', endpoint='stock-test'))
admin.add_view(TestView(CommodityTest, db.session, category='Commodity', name='Test', endpoint='commodity-test'))
#app.route('/')
def index():
return 'Click me to get to Admin!'
if __name__ == '__main__':
app.run()

Absolute paths on images uploaded by django-ckeditor

I am using django-rest-framework in conjuntion with django-ckeditor. I'm serving some images with absolute url-s without any problem. But images and files uploaded by ckeditor are served as relative paths, and they can't be displayed client side since it is in a different domain.
Here is an example of what I'm getting:
{
image: "http://example.com/media/myimage.png",
body: "<p>download my file</p>"
}
And this is what I woul like to get:
{
image: "http://example.com/media/myimage.png",
body: "<p>download my file</p>"
}
Edit:
This would be the model of my example:
from django.db import models
from ckeditor_uploader.fields import RichTextUploadingField
image: models.ImageField()
body: RichTextUploadingField(blank=True,null=True)
I would use a custom serializer to fix that:
from rest_framework import serializers
def relative_to_absolute(url):
return 'http://127.0.0.1:8000' + url
class FileFieldSerializer(serializers.Field):
def to_representation(self, value):
url = value.url
if url and url.startswith('/'):
url = relative_to_absolute(url)
return url
When filefield.url contains a relative url, relative_to_absolute() is called to prepend the domain.
Here I just used a constant string; you can either save it in your settings, or, if Django Site framework is installed, retrieve it as follows:
from django.contrib.sites.models import Site
domain=Site.objects.get_current().domain
Sample usage of the custom serializer:
class Picture(BaseModel):
...
image = models.ImageField(_('Image'), null=True, blank=True)
...
class PictureSerializer(serializers.ModelSerializer):
image = FileFieldSerializer()
class Meta:
model = Picture
fields = '__all__'
Variation for RichTextUploadingField
If, on the other hand, you're using RichTextUploadingField by CKEditor, your field is, basically, a TextField where an HTML fragment is saved upon images
upload.
In this HTML fragment, CKEditor will reference the uploaded images with a relative path, for very good reasons:
your site will still work if the domain is changed
the development instance will work in localhost
after all, we're using Django, not WordPress ;)
So, I wouldn't touch it, and fix the path at runtime in a custom serializer instead:
SEARCH_PATTERN = 'href=\\"/media/ckeditor/'
SITE_DOMAIN = "http://127.0.0.1:8000"
REPLACE_WITH = 'href=\\"%s/media/ckeditor/' % SITE_DOMAIN
class FixAbsolutePathSerializer(serializers.Field):
def to_representation(self, value):
text = value.replace(SEARCH_PATTERN, REPLACE_WITH)
return text
Alternatively, domain can be saved in settings:
from django.conf import settings
REPLACE_WITH = 'href=\\"%s/media/ckeditor/' % settings.SITE_DOMAIN
or retrieved from Django Site framework as follows:
from django.contrib.sites.models import Site
REPLACE_WITH = 'href=\\"{scheme}{domain}/media/ckeditor/'.format(
scheme="http://",
domain=Site.objects.get_current().domain
)
You might need to adjust SEARCH_PATTERN according to your CKEditor configuration; the more specific, the better.
Sample usage:
class Picture(BaseModel):
...
body = RichTextUploadingField(blank=True,null=True)
...
class PictureSerializer(serializers.ModelSerializer):
body = FixAbsolutePathSerializer()
class Meta:
model = Picture
fields = '__all__'

Django: How to automatically reset a boolean field to it's default after some time (eg. 6 months) to make full access for a page expire

I am fairly new to django and I have the problem of creating full access for a site. The user has to give some additional information to get full access after signing up. I want the full access to automatically expire after 6 months. I defined a custom user model with the extra condition:
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
full_name = models.CharField(blank=True, max_length=255)
has_full_access = models.BooleanField(default=False)
#some other stuff
After typing in some data for getting full access, the user gets redirected to this view which sets the boolean to true:
views.py
def data_gathered_done(request):
current_user = CustomUser.objects.get(id=request.user.id)
current_user.has_full_access = True
current_user.save()
#some other stuff
I want this boolean field to automatically reset to it's default (False) 6 months after the full access has been granted. How can I do that?
I'd do it with a property on the Model.
from datetime import datetime, timedelta
from django.db import models
from django.contrib.auth.models import AbstractUser
expire_after = timedelta(days=180)
class CustomUser(AbstractUser):
full_name = models.CharField(blank=True, max_length=255)
full_access_since = models.DatetimeField(auto_add_now=True)
#some other stuff
#property
def has_full_access(self):
return datetime.now() - expire_after < self.full_access_since
Then you can use the Boolean normally
from django.http import HttpResponseForbidden
user = CostumUser.objects.get(pk=123)
if not user.has_full_access:
return HttpResponseForbidden()
I'm a little late to this question, but I had a somewhat similar problem recently where I needed a boolean "lock" that "expired" after a 90 minutes. I didn't want to install any third-party dependencies or packages to do this.
The scenario: When a user accesses an "edit mode" view/template from a given model instance's detail view, I need to lock out all other users to prevent concurrency issues.
However after X minutes, I want others to be able to edit so I needed the UI menu options to revert back.
(Note: In my case I have to deal with concurrency at the database level as well, but this solution deals with the UI.) However, the logic could be extended to handle other time-based access issues within a site or webapp.
If I handled this only client side (say with AJAX), a user might lock a model and potentially their computer blows up, hence no AJAX fires to unlock. Has to be back-end. Like the answer above, a function that checks timestamps on the model seems like the way to go, but then again I have users all over the world - how do I deal with daylight savings and different timezones?
My solution was to use a non-DST timezone as a time constant so I didn't have to worry about that. Who cares what timezone I'm benchmarking - it's just a back-end method that checks durations.
models.py
class SomeProduct(models.Model):
name = models.CharField()
description = models.TextField()
lock = models.BooleanField(default=False)
timestamp = models.DateTimeField(null=True, blank=True, auto_now_add=False)
def __str__(self):
return str(self.name)
views.py
import datetime
import pytz
def update_product_view(request, slug): # This view shows forms and locks out other editors
qs = SomeProduct.objects.get(slug=slug):
if qs.lock == False:
qs.lock = True
now = datetime.datetime.now(pytz.timezone('US/Hawaii')) #Hawaii time is constant, no DST
qs.timestamp = now
qs.save()
elif qs.lock == True:
now = datetime.datetime.now(pytz.timezone('US/Hawaii'))
qs.timestamp = now
qs.save()
else:
pass
# Forms and other view logic here...
def product_view(request, slug): # This view unlocks the model if enough time has passed
qs = SomeProduct.objects.get(slug=slug):
if qs is not None:
try: # in case no timestamp has been set
now = datetime.datetime.now(pytz.timezone('US/Hawaii'))
then = qs.timestamp
delta = (now-then).total_seconds() # compare the difference
minutes = 60 #seconds
if delta > 90*minutes:
qs.lock = False # if 90 or more minutes have passed, unlock the model
qs.save()
else:
pass
except:
pass
else:
pass
# context and other view logic here...
template
{% if obj.lock == True %}
# adjust edit options or hide buttons accordingly
{% else obj.lock == False %}
# show button that leads to edit view url
{% endif %}
This is a pretty simplified version of my code, but the basics are there. I also have some JS on the front end that informs the user with a timeclock, exit edit mode URL that unlocks the model, etc. Your needs may vary. If anybody has some perspective on how I can make this better or any "gotchas" I'd love to learn something so please share. For now this works!

How to access the parent model of a Django-CMS plugin

I created 2 django-cms plugins, a parent "Container" that can contain multiple child "Content" plugins.
When I save the child plugin I would like to access the model of the parent plugin.
from cms.plugin_pool import plugin_pool
from cms.plugin_base import CMSPluginBase
from .models import Container, Content
class ContainerPlugin(CMSPluginBase):
model = Container
name = "Foo Container"
render_template = "my_package/container.html"
allow_children = True
child_classes = ["ContentPlugin"]
class ContentPlugin(CMSPluginBase):
model = content
name = "Bar Content"
render_template = "my_package/content.html"
require_parent = True
parent_classes = ["ContainerPlugin"]
allow_children = True
def save_model(self, request, obj, form, change):
response = super(ContentPlugin, self).save_model(
request, obj, form, change
)
# here I want to access the parent's (container) model, but how?
return response
plugin_pool.register_plugin(ContainerPlugin)
plugin_pool.register_plugin(ContentPlugin)
obj is the current plugin instance, so I can get all the properties of this model, but I can't figure out how to access the parent's plugin model. There is obj.parent, but it's not the plugin instance as far as I can tell. Also tried playing around with self.cms_plugin_instance and obj.parent.get_plugin_instance() but with no success.
Any advice?
Given a plugin instance,instance.get_plugin_instance() method returns a tuple containing:
instance - The plugin instance
plugin - the associated plugin class instance
get_plugin_instance
so something like this to get the parent object:
instance, plugin_class = object.parent.get_plugin_instance()
Option 1
Looking at the source code of CMSPluginBase, you might be able to use the implementation of get_child_classes. Unfortunately, that method really only returns the class names, so you cannot use it directly. But I think it actually does iterate over the child instances to get the class names:
def get_child_classes(self, slot, page):
from cms.utils.placeholder import get_placeholder_conf
template = page and page.get_template() or None
# config overrides..
ph_conf = get_placeholder_conf('child_classes', slot, template, default={})
child_classes = ph_conf.get(self.__class__.__name__, self.child_classes)
if child_classes:
return child_classes
from cms.plugin_pool import plugin_pool
installed_plugins = plugin_pool.get_all_plugins(slot, page)
return [cls.__name__ for cls in installed_plugins]
What you'd be interested in would be these two lines:
from cms.plugin_pool import plugin_pool
installed_plugins = plugin_pool.get_all_plugins(slot, page)
Option 2
Another way (the one I am using in my code) is to use signals, though this also requires finding the correct objects. The code is not very readable imho (see my lingering inline comments), but it works. It was written a while ago but I am still using it with django-cms 3.2.3.
The placeholder names really are the names that you have configured for your placeholders. It's certainly preferable to move that into the settings or somewhere. I'm not sure why I haven't done that, though.
I'd be interested in your solution!
# signals.py
import itertools
import logging
from cms.models import CMSPlugin
from cms.plugin_pool import plugin_pool
from django.db import ProgrammingError
from django.db.models.signals import post_save
logger = logging.getLogger(__name__)
_registered_plugins = [CMSPlugin.__name__]
def on_placeholder_saved(sender, instance, created, raw, using, update_fields, **kwargs):
"""
:param sender: Placeholder
:param instance: instance of Placeholder
"""
logger.debug("Placeholder SAVED: %s by sender %s", instance, sender)
# TODO this is totally ugly - is there no generic way to find out the related names?
placeholder_names = [
'topicintro_abstracts',
'topicintro_contents',
'topicintro_links',
'glossaryentry_explanations',
]
fetch_phs = lambda ph_name: _fetch_qs_as_list(instance, ph_name)
container = list(itertools.chain.from_iterable(map(fetch_phs, placeholder_names)))
logger.debug("Modified Placeholder Containers %s (%s)", container, placeholder_names)
if container:
if len(container) > 1:
raise ProgrammingError("Several Containers use the same placeholder.")
else:
# TODO change modified_by (if possible?)
container[0].save()
def _fetch_qs_as_list(instance, field):
"""
:param instance: a model
:param field: optional field (might not exist on model)
:return: the field values as list (not as RelatedManager)
"""
qs = getattr(instance, field)
fields = qs.all() if qs else []
return fields
def on_cmsplugin_saved(sender, instance, created, raw, using, update_fields, **kwargs):
"""
:param sender: CMSPlugin or subclass
:param instance: instance of CMSPlugin
"""
plugin_class = instance.get_plugin_class()
logger.debug("CMSPlugin SAVED: %s; plugin class: %s", instance, plugin_class)
if not plugin_class.name in _registered_plugins:
post_save.connect(on_cmsplugin_saved, sender=plugin_class)
_registered_plugins.append(plugin_class.name)
logger.info("Registered post_save listener with %s", plugin_class.name)
on_placeholder_saved(sender, instance.placeholder, created, raw, using, update_fields)
def connect_existing_plugins():
plugin_types = CMSPlugin.objects.order_by('plugin_type').values_list('plugin_type').distinct()
for plugin_type in plugin_types:
plugin_type = plugin_type[0]
if not plugin_type in _registered_plugins:
plugin_class = plugin_pool.get_plugin(plugin_type)
post_save.connect(on_cmsplugin_saved, sender=plugin_class)
post_save.connect(on_cmsplugin_saved, sender=plugin_class.model)
_registered_plugins.append(plugin_type)
_registered_plugins.append(plugin_class.model.__name__)
logger.debug("INIT registered plugins: %s", _registered_plugins)
post_save.connect(on_cmsplugin_saved, sender=CMSPlugin)
You have to setup these signals somewhere. I'm doing this in my urls.py, though the app config might be the suitable location for it? (I'm trying to avoid app configs.)
# This code has to run at server startup (and not during migrate if avoidable)
try:
signals.connect_existing_plugins()
except db.utils.ProgrammingError:
logger.warn('Failed to setup signals (if your DB is not setup (not tables), you can savely ignore this error.')
By the fact that children plugins always inherit the context. In the parent template you be able to do:
{% with something=instance.some_parent_field %}
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
{% endwith %}
And use something in your child template.

Render a Django CMS Placeholder to variable in view

I try to render a Django CMS Placeholder from a page to a variable, to return the rendered code as JSON.
So what I do is:
from cms.models.placeholdermodel import Placeholder
from cms.models.pagemodel import Page
def render(self, page_id, placeholder_slot, request):
page = Page.objects.get(id=page_id)
placeholder = page.placeholders.get(slot=placeholder_slot)
Now I want to render the placeholder to a variable. Which function do I have to call in which way to get this?
The Placeholder can be called manually and rendered like that, a few examples:
from cms import models
from django import template
def render(request):
placeholder = models.Placeholder.objects.get_or_create(slot='some_slot')
context = template.RequestContext(request)
return placeholder.render(request, width=None)
Or for json/javascript/etc. where you don't want full html but just the value:
def render_basic(request):
placeholder = models.Placeholder.objects.get_or_create(slot='some_slot')
context = template.RequestContext(request)
return placeholder.render(request, width=None, editable=False)
It's also possible (and even easier) to use the StaticPlaceholder:
from cms import models
def render(request):
placeholder = models.StaticPlaceholder.objects.get_or_create(name='name of the placeholder')
return placeholder.code
You can render it using Placeholder.render. Note that the context must contain a valid HttpRequest object under the 'request' key. width may be None. See the {% render_placeholder %} implementation for an example.