Skip to content
Snippets Groups Projects
Commit d5a6211d authored by Bryan BRANCOTTE's avatar Bryan BRANCOTTE
Browse files

extract live-settings

parent 64dab00b
No related branches found
No related tags found
1 merge request!249extract live-settings
Pipeline #150882 passed
Showing
with 2 additions and 405 deletions
from datetime import datetime
from django.core.cache import cache
from django.db import OperationalError, ProgrammingError
__LIVE_SETTING_EDITOR = "LiveSettingEditor"
def get_live_settings_editor_group_name():
return __LIVE_SETTING_EDITOR
class LiveSettings(object):
def __setattr__(self, key, value, *args, **kwargs):
try:
from live_settings.models import LiveSettings
if value is None:
LiveSettings.objects.filter(key=key).delete()
else:
LiveSettings.objects.update_or_create(key=key, defaults=dict(value=str(value)))
except (ProgrammingError, OperationalError):
return super().__setattr__(key, value)
def __getattribute__(self, key):
as_int = False
as_bool = False
as_date = False
if key.endswith("__int"):
key = key[:-5]
as_int = True
if key.endswith("__bool"):
key = key[:-6]
as_bool = True
if key.endswith("__date"):
key = key[:-6]
as_date = True
live_settings_dict = cache.get("live_settings_dict")
if live_settings_dict is None:
try:
from live_settings.models import LiveSettings
live_settings_dict = dict(LiveSettings.objects.values_list("key", "value"))
cache.set("live_settings_dict", live_settings_dict)
except (ProgrammingError, OperationalError):
return super().__getattribute__(key)
value = live_settings_dict.get(key, None)
if value is None:
return value
if as_int:
return int(value)
if as_bool:
return str(value).upper() == "TRUE"
if as_date:
return datetime.strptime(str(value), "%Y-%m-%d").date()
return value
live_settings = LiveSettings()
from django.contrib import admin
from live_settings import models
class LiveSettingsAdmin(admin.ModelAdmin):
list_display = ("key", "last_edition_date")
readonly_fields = ["last_edition_date"]
admin.site.register(models.LiveSettings, LiveSettingsAdmin)
from live_settings import live_settings
def processors(request):
return dict(live_settings=live_settings)
from django import forms
from live_settings import models
class LiveSettingsForm(forms.ModelForm):
next = forms.CharField(widget=forms.HiddenInput(), required=True)
class Meta:
model = models.LiveSettings
fields = ("value",)
widgets = {"value": forms.HiddenInput()}
# Generated by Django 2.2.5 on 2019-11-20 10:20
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='LiveSettings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(max_length=256, unique=True, validators=[django.core.validators.RegexValidator(code='invalid_live_settings_key', message='LiveSettings key must must be Alphanumeric and start with a letter: <code>^[a-zA-Z][\\w]*$</code>.', regex='^[a-zA-Z][\\w]*$')])),
('value', models.TextField(blank=True, null=True)),
('last_edition_date', models.DateTimeField()),
],
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth import get_permission_codename
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
from django.db import migrations
def migration_code(apps, schema_editor):
# inspired by django.contrib.auth.management.create_permissions
group, _ = Group.objects.get_or_create(name="LiveSettingEditor")
for klass in [
apps.get_model("live_settings", "LiveSettings"),
]:
ct = ContentType.objects.get_for_model(klass)
opts = klass._meta
for action in klass._meta.default_permissions:
perm, _ = Permission.objects.get_or_create(
codename=get_permission_codename(action, opts),
name='Can %s %s' % (action, opts.verbose_name_raw),
content_type=ct)
group.permissions.add(perm)
def reverse_code(apps, schema_editor):
Group.objects.filter(name="LiveSettingEditor").delete()
class Migration(migrations.Migration):
dependencies = [
('live_settings', '0001_initial'),
]
operations = [
migrations.RunPython(migration_code, reverse_code=reverse_code),
]
# Generated by Django 2.2.5 on 2020-03-11 09:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('live_settings', '0002_create_group_settings_changer'),
]
operations = [
migrations.AlterModelOptions(
name='livesettings',
options={'verbose_name': 'Live setting', 'verbose_name_plural': 'Live settings'},
),
]
# Generated by Django 3.2.4 on 2021-07-01 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('live_settings', '0003_auto_20200311_1037'),
]
operations = [
migrations.AlterField(
model_name='livesettings',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]
from __future__ import unicode_literals
from django.core.cache import cache
from django.core.validators import RegexValidator
from django.db import models
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.utils import timezone
from django.utils.safestring import mark_safe
class LiveSettings(models.Model):
class Meta:
verbose_name = "Live setting"
verbose_name_plural = "Live settings"
regex = "^[a-zA-Z][\\w]*$"
key = models.CharField(
max_length=256,
unique=True,
validators=(
RegexValidator(
regex=regex,
message=mark_safe(
"LiveSettings key must must be Alphanumeric and start with a letter: " "<code>%s</code>." % regex
),
code="invalid_live_settings_key",
),
),
)
value = models.TextField(
blank=True,
null=True,
)
last_edition_date = models.DateTimeField()
def save(self, *args, **kwargs):
self.last_edition_date = timezone.now()
super().save(*args, **kwargs)
@receiver(post_save, sender=LiveSettings)
@receiver(post_delete, sender=LiveSettings)
def flush_live_settings_in_cache(*args, **kwargs):
cache.delete("live_settings_dict")
{% extends "admin/index.html" %}
{% load i18n basetheme_bootstrap %}
{% block extrastyle %}
{{ block.super }}
<style>
#content-related{
background: none;
}
#content-related .module{
background: var(--darkened-bg);
}
#site-wide-commands form{
margin:15px 7px;
}
</style>
{% endblock %}
{% block sidebar %}
<div id="content-related">
{% if perms.live_settings.change_livesettings %}
<div class="module" id="site-wide-commands">
<h2>{% trans 'Site wide settings' %}</h2>
{%include_if_exists "live_settings/sitewidecommands.html" %}
</div>
{%endif%}
<div class="module" id="recent-actions-module">
<h2>{% trans 'Recent actions' %}</h2>
<h3>{% trans 'My actions' %}</h3>
{% load log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
<p>{% trans 'None available' %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
{% if entry.is_deletion or not entry.get_admin_url %}
{{ entry.object_repr }}
{% else %}
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
{% endif %}
<br>
{% if entry.content_type %}
<span class="mini quiet">{% filter capfirst %}{{ entry.content_type }}{% endfilter %}</span>
{% else %}
<span class="mini quiet">{% trans 'Unknown content' %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
<hr/>
</div>
</div>
{% endblock %}
\ No newline at end of file
import logging
from unittest import TestCase
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core import management
from django.core.cache import cache
from django.test import TestCase as DjangoTestCase
from django.urls import reverse
from live_settings import live_settings, models
from live_settings.tools import set_default_live_setting
logger = logging.getLogger(__name__)
class LiveSettingsTestCase(DjangoTestCase):
def setUp(self) -> None:
super().setUp()
################################################################################
self.user = get_user_model().objects.create(
username="root",
)
def test_get_value(self):
self.assertIsNone(live_settings.toto)
models.LiveSettings.objects.create(key="toto", value="tata")
self.assertIsNotNone(live_settings.toto)
self.assertEqual(live_settings.toto, "tata")
def test_get_set(self):
live_settings.tralala = 1
self.assertEqual(str(live_settings.tralala), "1")
live_settings.tralala = None
self.assertIsNone(live_settings.tralala)
def test_view_works(self):
form_data = dict(value="titi", next="/")
url = reverse('live_settings:update', args=["toto"])
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 302)
self.assertNotEqual(live_settings.toto, form_data["value"])
self.client.force_login(self.user)
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 302)
self.assertNotEqual(live_settings.toto, form_data["value"])
change = Permission.objects.get(
content_type=ContentType.objects.get_for_model(models.LiveSettings), codename__startswith="change"
)
self.user.user_permissions.add(change)
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
self.assertNotEqual(live_settings.toto, form_data["value"])
response = self.client.post(url, dict(value=form_data["value"]))
self.assertEqual(response.status_code, 400)
self.assertNotEqual(live_settings.toto, form_data["value"])
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 302)
self.assertEqual(live_settings.toto, form_data["value"])
class LiveSettingsNoDBTestCase(TestCase):
def test_get_value(self):
management.call_command("migrate", "live_settings", "zero", no_input=True, skip_checks=True)
cache.delete("live_settings_dict")
try:
ex = live_settings.tralala
except Exception as e:
ex = e
assert isinstance(ex, AttributeError)
live_settings.tralala = 1
set_default_live_setting("tralala", 2)
from django.core.cache import cache
from django.db import OperationalError, ProgrammingError
def set_default_live_setting(key, value):
try:
from live_settings.models import LiveSettings
LiveSettings.objects.get_or_create(key=key, defaults=dict(value=str(value)))
cache.delete("live_settings_dict")
except (ProgrammingError, OperationalError):
pass
from django.urls import re_path
from live_settings import views
app_name = "live_settings"
urlpatterns = [re_path(r"^update/(?P<slug>[a-zA-Z][\w]*)/$", views.change_value, name="update")]
from django.contrib.auth.decorators import permission_required
from django.http import HttpResponseForbidden, HttpResponseBadRequest
from django.shortcuts import redirect
from live_settings import forms
from live_settings import live_settings
@permission_required("live_settings.change_livesettings")
def change_value(request, slug):
if request.method != "POST":
return HttpResponseForbidden()
form = forms.LiveSettingsForm(data=request.POST)
if not form.is_valid():
return HttpResponseBadRequest()
setattr(live_settings, slug, form.cleaned_data["value"])
return redirect(form.cleaned_data["next"])
...@@ -9,6 +9,8 @@ django-basetheme-bootstrap>=1.8.7 ...@@ -9,6 +9,8 @@ django-basetheme-bootstrap>=1.8.7
django-kubernetes-probes>=1.1 django-kubernetes-probes>=1.1
--extra-index-url https://gitlab.pasteur.fr/api/v4/projects/hub%2Fdjango-csp-mail-reports/packages/pypi/simple --extra-index-url https://gitlab.pasteur.fr/api/v4/projects/hub%2Fdjango-csp-mail-reports/packages/pypi/simple
django-csp-mail-reports>=1.0 django-csp-mail-reports>=1.0
--extra-index-url https://gitlab.pasteur.fr/api/v4/projects/hub%2Fdjango-live-settings/packages/pypi/simple
django-live-settings>=1.0
django-formtools django-formtools
csscompressor csscompressor
coverage coverage
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment