Skip to content

Commit 0f4d8d8

Browse files
author
Krzysztof Socha
committedJun 12, 2018
Initial commit
0 parents  commit 0f4d8d8

18 files changed

+456
-0
lines changed
 

‎.editorconfig

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# editorconfig.org
2+
3+
root = true
4+
5+
[*]
6+
indent_style = space
7+
indent_size = 4
8+
end_of_line = lf
9+
charset = utf-8
10+
trim_trailing_whitespace = true
11+
insert_final_newline = true
12+
13+
[*.py]
14+
max_line_length = 120
15+
quote_type = single
16+
17+
[*.{scss,js,html}]
18+
max_line_length = 120
19+
indent_style = space
20+
quote_type = double
21+
22+
[*.js]
23+
max_line_length = 120
24+
quote_type = single
25+
26+
[*.rst]
27+
max_line_length = 80
28+
29+
[*.yml]
30+
indent_size = 2

‎.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
*.pyc
2+
*.egg-info
3+
.DS_Store
4+
.idea/
5+
.tox/
6+
.eggs/
7+
dist/
8+
build/
9+
.env
10+
local.sqlite
11+
.coverage
12+
htmlcov/

‎LICENSE.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Copyright (c) 2018, Divio AG
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above copyright
9+
notice, this list of conditions and the following disclaimer in the
10+
documentation and/or other materials provided with the distribution.
11+
* Neither the name of Divio AG nor the
12+
names of its contributors may be used to endorse or promote products
13+
derived from this software without specific prior written permission.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
DISCLAIMED. IN NO EVENT SHALL DIVIO AG BE LIABLE FOR ANY
19+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

‎MANIFEST.in

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include LICENSE.txt
2+
include README.rst
3+
recursive-exclude * *.pyc

‎README.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
****************
2+
django CMS Versioning
3+
****************
4+
5+
============
6+
Installation
7+
============
8+
9+
Requirements
10+
============
11+
12+
django CMS URL Manager requires that you have a django CMS 3.6 (or higher) project already running and set up.
13+
14+
15+
To install
16+
==========
17+
18+
Run::
19+
20+
pip install djangocms-versioning
21+
22+
Add ``djangocms_versioning`` to your project's ``INSTALLED_APPS``.
23+
24+
Run::
25+
26+
python manage.py migrate djangocms_versioning
27+
28+
to perform the application's database migrations.
29+
30+
31+
=====
32+
Usage
33+
=====

‎djangocms_versioning/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__version__ = '0.0.0'
2+
3+
default_app_config = 'djangocms_versioning.apps.VersioningConfig'

‎djangocms_versioning/apps.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.apps import AppConfig
2+
from django.utils.translation import ugettext_lazy as _
3+
4+
5+
class VersioningConfig(AppConfig):
6+
name = 'djangocms_versioning'
7+
verbose_name = _('django CMS Versioning')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.13 on 2018-06-12 12:21
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Campaign',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('name', models.TextField()),
21+
('start', models.DateTimeField(blank=True, null=True)),
22+
('end', models.DateTimeField(blank=True, null=True)),
23+
],
24+
),
25+
]

‎djangocms_versioning/migrations/__init__.py

Whitespace-only changes.

‎djangocms_versioning/models.py

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from django.db import connections, models
2+
from django.db.models import Max, Q
3+
from django.utils.timezone import localtime
4+
5+
6+
class Campaign(models.Model):
7+
name = models.TextField()
8+
start = models.DateTimeField(null=True, blank=True)
9+
end = models.DateTimeField(null=True, blank=True)
10+
11+
12+
class BaseVersionQuerySet(models.QuerySet):
13+
14+
# extra_filters = Q(content__language='en')
15+
def for_grouper(self, grouper, extra_filters=None):
16+
if extra_filters is None:
17+
extra_filters = Q()
18+
return self.filter(
19+
Q(extra_filters) &
20+
Q((self.model.grouper_field, grouper)),
21+
)
22+
23+
def _public_qs(self, when=None):
24+
if when is None:
25+
when = localtime()
26+
return self.filter(
27+
(
28+
Q(start__lte=when) |
29+
Q(campaign__start__lte=when)
30+
) & (
31+
Q(end__gte=when) |
32+
Q(end__isnull=True) |
33+
(
34+
Q(campaign__isnull=False) &
35+
(
36+
Q(campaign__end__gte=when) |
37+
Q(campaign__end__isnull=True)
38+
)
39+
)
40+
) & Q(is_active=True)
41+
).order_by('-created')
42+
43+
def public(self, when=None):
44+
if when is None:
45+
when = localtime()
46+
return self._public_qs(when).first()
47+
48+
def distinct_groupers(self):
49+
if connections[self.db].features.can_distinct_on_fields:
50+
# ?
51+
return self.distinct(self.model.grouper_field).order_by('-created')
52+
else:
53+
inner = self.values(
54+
self.model.grouper_field,
55+
).annotate(Max('pk')).values('pk__max')
56+
return self.filter(pk__in=inner)
57+
58+
59+
class BaseVersion(models.Model):
60+
COPIED_FIELDS = ['label', 'campaign', 'start', 'end', 'is_active']
61+
62+
label = models.TextField()
63+
campaign = models.ForeignKey(
64+
Campaign,
65+
on_delete=models.CASCADE,
66+
null=True,
67+
blank=True,
68+
)
69+
created = models.DateTimeField(auto_now_add=True)
70+
start = models.DateTimeField(null=True, blank=True)
71+
end = models.DateTimeField(null=True, blank=True)
72+
is_active = models.BooleanField(default=True)
73+
74+
objects = BaseVersionQuerySet.as_manager()
75+
76+
class Meta:
77+
abstract = True
78+
79+
def _copy_func_factory(self, name):
80+
def inner(new):
81+
related = getattr(self, name)
82+
related.pk = None
83+
related.save()
84+
return related
85+
return inner
86+
87+
def _get_relation_fields(self):
88+
"""Returns a list of relation fields to copy over.
89+
If copy_field_order is present, sorts the outcome
90+
based on the list of field names
91+
"""
92+
relation_fields = [
93+
f for f in self._meta.get_fields() if
94+
f.is_relation and
95+
f.name not in self.COPIED_FIELDS and
96+
not f.auto_created
97+
]
98+
if getattr(self, 'copy_field_order', None):
99+
relation_fields = sorted(
100+
relation_fields,
101+
key=lambda f: self.copy_field_order.index(f.name),
102+
)
103+
return relation_fields
104+
105+
def copy(self):
106+
new = self._meta.model(**{
107+
f: getattr(self, f)
108+
for f in self.COPIED_FIELDS
109+
})
110+
m2m_cache = {}
111+
relation_fields = self._get_relation_fields()
112+
for f in relation_fields:
113+
try:
114+
copy_func = getattr(self, 'copy_{}'.format(f.name))
115+
except AttributeError:
116+
copy_func = self._copy_func_factory(f.name)
117+
new_value = copy_func(new)
118+
if f.many_to_many:
119+
m2m_cache[f.name] = new_value
120+
else:
121+
setattr(new, f.name, new_value)
122+
new.save()
123+
for field, value in m2m_cache.items():
124+
getattr(new, field).set(value)
125+
return new

‎djangocms_versioning/test_utils/__init__.py

Whitespace-only changes.

‎djangocms_versioning/test_utils/polls/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from django.db import models
2+
from djangocms_versioning.models import BaseVersion
3+
4+
5+
class Poll(models.Model):
6+
name = models.TextField()
7+
8+
def __str__(self):
9+
return "{} ({})".format(self.name, self.pk)
10+
11+
12+
class PollContent(models.Model):
13+
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
14+
language = models.TextField()
15+
text = models.TextField()
16+
17+
def __str__(self):
18+
return self.text
19+
20+
21+
class Answer(models.Model):
22+
poll_content = models.ForeignKey(PollContent, on_delete=models.CASCADE)
23+
text = models.TextField()
24+
25+
def __str__(self):
26+
return self.text
27+
28+
29+
class PollVersion(BaseVersion):
30+
grouper_field = 'content__poll'
31+
copy_field_order = ('content', 'answers')
32+
33+
content = models.OneToOneField(PollContent, on_delete=models.CASCADE)
34+
answers = models.ManyToManyField(Answer)
35+
36+
def copy_answers(self, new):
37+
return [
38+
Answer.objects.create(
39+
text=answer.text,
40+
poll_content=new.content,
41+
) for answer in self.answers.all()
42+
]

‎setup.cfg

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[flake8]
2+
exclude =
3+
.git,
4+
__pycache__,
5+
**/migrations/,
6+
build/,
7+
.tox/,
8+
9+
[isort]
10+
line_length = 79
11+
multi_line_output = 3
12+
lines_after_imports = 2
13+
include_trailing_comma = true
14+
balanced_wrapping = true
15+
skip = manage.py, migrations, .tox
16+
known_third_party = django
17+
known_cms = cms, menus
18+
known_first_party = djangocms_versioning
19+
sections = FUTURE, STDLIB, THIRDPARTY, CMS, FIRSTPARTY, LIB, LOCALFOLDER
20+
21+
[coverage:run]
22+
branch = True
23+
source = djangocms_versioning
24+
omit =
25+
*apps.py,
26+
*constants.py,
27+
*migrations/*,
28+
*test_utils/*,
29+
*tests/*,
30+
31+
[coverage:report]
32+
exclude_lines =
33+
pragma: no cover
34+
def __repr__
35+
if self.debug:
36+
if settings.DEBUG
37+
raise AssertionError
38+
raise NotImplementedError
39+
if 0:
40+
if __name__ == .__main__.:

‎setup.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from setuptools import find_packages, setup
2+
3+
import djangocms_versioning
4+
5+
6+
INSTALL_REQUIREMENTS = [
7+
'Django>=1.11,<2.1',
8+
'django-cms>=3.5.0',
9+
]
10+
11+
12+
setup(
13+
name='djangocms-versioning',
14+
packages=find_packages(),
15+
include_package_data=True,
16+
version=djangocms_versioning.__version__,
17+
description=djangocms_versioning.__doc__,
18+
long_description=open('README.rst').read(),
19+
classifiers=[
20+
'Framework :: Django',
21+
'Intended Audience :: Developers',
22+
'License :: OSI Approved :: BSD License',
23+
'Operating System :: OS Independent',
24+
'Topic :: Software Development'
25+
],
26+
install_requires=INSTALL_REQUIREMENTS,
27+
author='Divio AG',
28+
author_email='info@divio.ch',
29+
url='http://github.com/divio/djangocms-versioning',
30+
license='BSD',
31+
)

‎test_settings.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
HELPER_SETTINGS = {
2+
'TIME_ZONE': 'America/Chicago',
3+
'INSTALLED_APPS': [
4+
'djangocms_versioning',
5+
],
6+
'CMS_PERMISSION': True,
7+
'LANGUAGES': (
8+
('en', 'English'),
9+
('de', 'German'),
10+
('fr', 'French'),
11+
('it', 'Italiano'),
12+
),
13+
'CMS_LANGUAGES': {
14+
1: [
15+
{
16+
'code': 'en',
17+
'name': 'English',
18+
'fallbacks': ['de', 'fr']
19+
},
20+
{
21+
'code': 'de',
22+
'name': 'Deutsche',
23+
'fallbacks': ['en'] # FOR TESTING DO NOT ADD 'fr' HERE
24+
},
25+
{
26+
'code': 'fr',
27+
'name': 'Française',
28+
'fallbacks': ['en'] # FOR TESTING DO NOT ADD 'de' HERE
29+
},
30+
{
31+
'code': 'it',
32+
'name': 'Italiano',
33+
'fallbacks': ['fr'] # FOR TESTING, LEAVE AS ONLY 'fr'
34+
},
35+
],
36+
},
37+
'PARLER_ENABLE_CACHING': False,
38+
'LANGUAGE_CODE': 'en',
39+
}
40+
41+
42+
def run():
43+
from djangocms_helper import runner
44+
runner.cms('djangocms_versioning', extra_args=[])
45+
46+
47+
if __name__ == "__main__":
48+
run()

‎tests/requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
coverage
2+
djangocms_helper
3+
flake8
4+
isort

‎tox.ini

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[tox]
2+
envlist =
3+
flake8
4+
py{34,35,36}-dj111-cms{35,36}
5+
; py{34,35,36}-dj{111,20}-cms36
6+
7+
skip_missing_interpreters=True
8+
9+
10+
[testenv]
11+
deps =
12+
-r{toxinidir}/dev_requirements.txt
13+
14+
dj111: Django>=1.11,<2.0
15+
dj20: Django>=2.0,<2.1
16+
17+
cms35: django-cms>=3.5,<3.6
18+
cms36: https://github.com/divio/django-cms/archive/develop.zip
19+
20+
basepython =
21+
py34: python3.4
22+
py35: python3.5
23+
py36: python3.6
24+
25+
commands =
26+
{envpython} --version
27+
{env:COMMAND:coverage} erase
28+
{env:COMMAND:coverage} run setup.py test
29+
{env:COMMAND:coverage} report

0 commit comments

Comments
 (0)
Please sign in to comment.