Merge pull request #4140 from ostcar/autoupdate_and_delete
Autoupdate on element deletion
This commit is contained in:
commit
6aeedf39fd
33
openslides/agenda/migrations/0006_auto_20190119_1425.py
Normal file
33
openslides/agenda/migrations/0006_auto_20190119_1425.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-01-19 13:25
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("agenda", "0005_auto_20180815_1109")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="item",
|
||||||
|
name="parent",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="children",
|
||||||
|
to="agenda.Item",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="speaker",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=openslides.utils.models.CASCADE_AND_AUTOUODATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -15,6 +15,7 @@ from openslides.utils.exceptions import OpenSlidesError
|
|||||||
from openslides.utils.models import RESTModelMixin
|
from openslides.utils.models import RESTModelMixin
|
||||||
from openslides.utils.utils import to_roman
|
from openslides.utils.utils import to_roman
|
||||||
|
|
||||||
|
from ..utils.models import CASCADE_AND_AUTOUODATE, SET_NULL_AND_AUTOUPDATE
|
||||||
from .access_permissions import ItemAccessPermissions
|
from .access_permissions import ItemAccessPermissions
|
||||||
|
|
||||||
|
|
||||||
@ -233,7 +234,7 @@ class Item(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
"self",
|
"self",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="children",
|
related_name="children",
|
||||||
@ -374,7 +375,7 @@ class Speaker(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
objects = SpeakerManager()
|
objects = SpeakerManager()
|
||||||
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE_AND_AUTOUODATE)
|
||||||
"""
|
"""
|
||||||
ForeinKey to the user who speaks.
|
ForeinKey to the user who speaks.
|
||||||
"""
|
"""
|
||||||
|
31
openslides/assignments/migrations/0006_auto_20190119_1425.py
Normal file
31
openslides/assignments/migrations/0006_auto_20190119_1425.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-01-19 13:25
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("assignments", "0005_auto_20180822_1042")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="assignmentoption",
|
||||||
|
name="candidate",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="assignmentrelateduser",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=openslides.utils.models.CASCADE_AND_AUTOUODATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -21,6 +21,7 @@ from openslides.utils.autoupdate import inform_changed_data
|
|||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import RESTModelMixin
|
from openslides.utils.models import RESTModelMixin
|
||||||
|
|
||||||
|
from ..utils.models import CASCADE_AND_AUTOUODATE, SET_NULL_AND_AUTOUPDATE
|
||||||
from .access_permissions import AssignmentAccessPermissions
|
from .access_permissions import AssignmentAccessPermissions
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
|||||||
ForeinKey to the assignment.
|
ForeinKey to the assignment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE_AND_AUTOUODATE)
|
||||||
"""
|
"""
|
||||||
ForeinKey to the user who is related to the assignment.
|
ForeinKey to the user who is related to the assignment.
|
||||||
"""
|
"""
|
||||||
@ -366,7 +367,9 @@ class AssignmentOption(RESTModelMixin, BaseOption):
|
|||||||
poll = models.ForeignKey(
|
poll = models.ForeignKey(
|
||||||
"AssignmentPoll", on_delete=models.CASCADE, related_name="options"
|
"AssignmentPoll", on_delete=models.CASCADE, related_name="options"
|
||||||
)
|
)
|
||||||
candidate = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
candidate = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL, on_delete=SET_NULL_AND_AUTOUPDATE, null=True
|
||||||
|
)
|
||||||
weight = models.IntegerField(default=0)
|
weight = models.IntegerField(default=0)
|
||||||
|
|
||||||
vote_class = AssignmentVote
|
vote_class = AssignmentVote
|
||||||
|
@ -5,14 +5,10 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("core", "0010_auto_20190118_1908")]
|
||||||
('core', '0010_auto_20190118_1908'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='history',
|
model_name="history", name="now", field=models.DateTimeField()
|
||||||
name='now',
|
)
|
||||||
field=models.DateTimeField(),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
41
openslides/core/migrations/0012_auto_20190119_1425.py
Normal file
41
openslides/core/migrations/0012_auto_20190119_1425.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-01-19 13:25
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("core", "0011_auto_20190119_0958")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="chatmessage",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=openslides.utils.models.CASCADE_AND_AUTOUODATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="history",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="projectiondefault",
|
||||||
|
name="projector",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="projectiondefaults",
|
||||||
|
to="core.Projector",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -6,7 +6,11 @@ from jsonfield import JSONField
|
|||||||
|
|
||||||
from ..utils.autoupdate import Element
|
from ..utils.autoupdate import Element
|
||||||
from ..utils.cache import element_cache, get_element_id
|
from ..utils.cache import element_cache, get_element_id
|
||||||
from ..utils.models import RESTModelMixin
|
from ..utils.models import (
|
||||||
|
CASCADE_AND_AUTOUODATE,
|
||||||
|
SET_NULL_AND_AUTOUPDATE,
|
||||||
|
RESTModelMixin,
|
||||||
|
)
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
ChatMessageAccessPermissions,
|
ChatMessageAccessPermissions,
|
||||||
ConfigAccessPermissions,
|
ConfigAccessPermissions,
|
||||||
@ -108,7 +112,7 @@ class ProjectionDefault(RESTModelMixin, models.Model):
|
|||||||
display_name = models.CharField(max_length=256)
|
display_name = models.CharField(max_length=256)
|
||||||
|
|
||||||
projector = models.ForeignKey(
|
projector = models.ForeignKey(
|
||||||
Projector, on_delete=models.CASCADE, related_name="projectiondefaults"
|
Projector, on_delete=models.PROTECT, related_name="projectiondefaults"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_root_rest_element(self):
|
def get_root_rest_element(self):
|
||||||
@ -179,7 +183,7 @@ class ChatMessage(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE_AND_AUTOUODATE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
@ -269,7 +273,7 @@ class HistoryManager(models.Manager):
|
|||||||
history_time = now()
|
history_time = now()
|
||||||
for element in elements:
|
for element in elements:
|
||||||
if (
|
if (
|
||||||
element["disable_history"]
|
element.get("disable_history")
|
||||||
or element["collection_string"]
|
or element["collection_string"]
|
||||||
== self.model.get_collection_string()
|
== self.model.get_collection_string()
|
||||||
):
|
):
|
||||||
@ -282,8 +286,8 @@ class HistoryManager(models.Manager):
|
|||||||
element["collection_string"], element["id"]
|
element["collection_string"], element["id"]
|
||||||
),
|
),
|
||||||
now=history_time,
|
now=history_time,
|
||||||
information=element["information"],
|
information=element.get("information", ""),
|
||||||
user_id=element["user_id"],
|
user_id=element.get("user_id"),
|
||||||
full_data=data,
|
full_data=data,
|
||||||
)
|
)
|
||||||
instance.save(
|
instance.save(
|
||||||
@ -308,9 +312,6 @@ class HistoryManager(models.Manager):
|
|||||||
id=full_data["id"],
|
id=full_data["id"],
|
||||||
collection_string=collection_string,
|
collection_string=collection_string,
|
||||||
full_data=full_data,
|
full_data=full_data,
|
||||||
information="",
|
|
||||||
user_id=None,
|
|
||||||
disable_history=False,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
instances = self.add_elements(elements)
|
instances = self.add_elements(elements)
|
||||||
@ -336,7 +337,7 @@ class History(RESTModelMixin, models.Model):
|
|||||||
information = models.CharField(max_length=255)
|
information = models.CharField(max_length=255)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL
|
settings.AUTH_USER_MODEL, null=True, on_delete=SET_NULL_AND_AUTOUPDATE
|
||||||
)
|
)
|
||||||
|
|
||||||
full_data = models.OneToOneField(HistoryData, on_delete=models.CASCADE)
|
full_data = models.OneToOneField(HistoryData, on_delete=models.CASCADE)
|
||||||
|
23
openslides/mediafiles/migrations/0003_auto_20190119_1425.py
Normal file
23
openslides/mediafiles/migrations/0003_auto_20190119_1425.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-01-19 13:25
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("mediafiles", "0002_mediafile_private")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="mediafile",
|
||||||
|
name="uploader",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
@ -3,7 +3,7 @@ from django.db import models
|
|||||||
|
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
from ..utils.autoupdate import inform_changed_data
|
from ..utils.autoupdate import inform_changed_data
|
||||||
from ..utils.models import RESTModelMixin
|
from ..utils.models import SET_NULL_AND_AUTOUPDATE, RESTModelMixin
|
||||||
from .access_permissions import MediafileAccessPermissions
|
from .access_permissions import MediafileAccessPermissions
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class Mediafile(RESTModelMixin, models.Model):
|
|||||||
"""A string representing the title of the file."""
|
"""A string representing the title of the file."""
|
||||||
|
|
||||||
uploader = models.ForeignKey(
|
uploader = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True
|
settings.AUTH_USER_MODEL, on_delete=SET_NULL_AND_AUTOUPDATE, null=True
|
||||||
)
|
)
|
||||||
"""A user – the uploader of a file."""
|
"""A user – the uploader of a file."""
|
||||||
|
|
||||||
|
@ -6,20 +6,20 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("motions", "0018_auto_20190118_2101")]
|
||||||
('motions', '0018_auto_20190118_2101'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='motion',
|
model_name="motion",
|
||||||
name='created',
|
name="created",
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='motion',
|
model_name="motion",
|
||||||
name='last_modified',
|
name="last_modified",
|
||||||
field=models.DateTimeField(auto_now=True),
|
field=models.DateTimeField(auto_now=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
123
openslides/motions/migrations/0020_auto_20190119_1425.py
Normal file
123
openslides/motions/migrations/0020_auto_20190119_1425.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-01-19 13:25
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("motions", "0019_auto_20190119_1025")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motion",
|
||||||
|
name="category",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to="motions.Category",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motion",
|
||||||
|
name="motion_block",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to="motions.MotionBlock",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motion",
|
||||||
|
name="parent",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="amendments",
|
||||||
|
to="motions.Motion",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motion",
|
||||||
|
name="recommendation",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="+",
|
||||||
|
to="motions.State",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motion",
|
||||||
|
name="sort_parent",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="children",
|
||||||
|
to="motions.Motion",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motion",
|
||||||
|
name="statute_paragraph",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="motions",
|
||||||
|
to="motions.StatuteParagraph",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motionchangerecommendation",
|
||||||
|
name="author",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motionchangerecommendation",
|
||||||
|
name="motion",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=openslides.utils.models.CASCADE_AND_AUTOUODATE,
|
||||||
|
related_name="change_recommendations",
|
||||||
|
to="motions.Motion",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="motionlog",
|
||||||
|
name="person",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="submitter",
|
||||||
|
name="user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=openslides.utils.models.CASCADE_AND_AUTOUODATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="workflow",
|
||||||
|
name="first_state",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="+",
|
||||||
|
to="motions.State",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -23,6 +23,7 @@ from openslides.utils.autoupdate import inform_changed_data
|
|||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import RESTModelMixin
|
from openslides.utils.models import RESTModelMixin
|
||||||
|
|
||||||
|
from ..utils.models import CASCADE_AND_AUTOUODATE, SET_NULL_AND_AUTOUPDATE
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
CategoryAccessPermissions,
|
CategoryAccessPermissions,
|
||||||
MotionAccessPermissions,
|
MotionAccessPermissions,
|
||||||
@ -141,7 +142,7 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
recommendation = models.ForeignKey(
|
recommendation = models.ForeignKey(
|
||||||
"State", related_name="+", on_delete=models.SET_NULL, null=True
|
"State", related_name="+", on_delete=SET_NULL_AND_AUTOUPDATE, null=True
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
The recommendation of a person or committee for this motion.
|
The recommendation of a person or committee for this motion.
|
||||||
@ -171,7 +172,7 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
sort_parent = models.ForeignKey(
|
sort_parent = models.ForeignKey(
|
||||||
"self",
|
"self",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="children",
|
related_name="children",
|
||||||
@ -181,14 +182,14 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
"Category", on_delete=models.SET_NULL, null=True, blank=True
|
"Category", on_delete=SET_NULL_AND_AUTOUPDATE, null=True, blank=True
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
ForeignKey to one category of motions.
|
ForeignKey to one category of motions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
motion_block = models.ForeignKey(
|
motion_block = models.ForeignKey(
|
||||||
"MotionBlock", on_delete=models.SET_NULL, null=True, blank=True
|
"MotionBlock", on_delete=SET_NULL_AND_AUTOUPDATE, null=True, blank=True
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
ForeignKey to one block of motions.
|
ForeignKey to one block of motions.
|
||||||
@ -207,7 +208,7 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
"self",
|
"self",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="amendments",
|
related_name="amendments",
|
||||||
@ -220,7 +221,7 @@ class Motion(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
statute_paragraph = models.ForeignKey(
|
statute_paragraph = models.ForeignKey(
|
||||||
StatuteParagraph,
|
StatuteParagraph,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="motions",
|
related_name="motions",
|
||||||
@ -704,7 +705,7 @@ class Submitter(RESTModelMixin, models.Model):
|
|||||||
Use custom Manager.
|
Use custom Manager.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE_AND_AUTOUODATE)
|
||||||
"""
|
"""
|
||||||
ForeignKey to the user who is the submitter.
|
ForeignKey to the user who is the submitter.
|
||||||
"""
|
"""
|
||||||
@ -754,7 +755,7 @@ class MotionChangeRecommendation(RESTModelMixin, models.Model):
|
|||||||
objects = MotionChangeRecommendationManager()
|
objects = MotionChangeRecommendationManager()
|
||||||
|
|
||||||
motion = models.ForeignKey(
|
motion = models.ForeignKey(
|
||||||
Motion, on_delete=models.CASCADE, related_name="change_recommendations"
|
Motion, on_delete=CASCADE_AND_AUTOUODATE, related_name="change_recommendations"
|
||||||
)
|
)
|
||||||
"""The motion to which the change recommendation belongs."""
|
"""The motion to which the change recommendation belongs."""
|
||||||
|
|
||||||
@ -780,7 +781,7 @@ class MotionChangeRecommendation(RESTModelMixin, models.Model):
|
|||||||
"""The replacement for the section of the original text specified by motion, line_from and line_to"""
|
"""The replacement for the section of the original text specified by motion, line_from and line_to"""
|
||||||
|
|
||||||
author = models.ForeignKey(
|
author = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True
|
settings.AUTH_USER_MODEL, on_delete=SET_NULL_AND_AUTOUPDATE, null=True
|
||||||
)
|
)
|
||||||
"""A user object, who created this change recommendation. Optional."""
|
"""A user object, who created this change recommendation. Optional."""
|
||||||
|
|
||||||
@ -921,7 +922,7 @@ class MotionLog(RESTModelMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
person = models.ForeignKey(
|
person = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True
|
settings.AUTH_USER_MODEL, on_delete=SET_NULL_AND_AUTOUPDATE, null=True
|
||||||
)
|
)
|
||||||
"""A user object, who created the log message. Optional."""
|
"""A user object, who created the log message. Optional."""
|
||||||
|
|
||||||
@ -1192,7 +1193,7 @@ class Workflow(RESTModelMixin, models.Model):
|
|||||||
"""A string representing the workflow."""
|
"""A string representing the workflow."""
|
||||||
|
|
||||||
first_state = models.OneToOneField(
|
first_state = models.OneToOneField(
|
||||||
State, on_delete=models.SET_NULL, related_name="+", null=True, blank=True
|
State, on_delete=models.CASCADE, related_name="+", null=True
|
||||||
)
|
)
|
||||||
"""A one-to-one relation to a state, the starting point for the workflow."""
|
"""A one-to-one relation to a state, the starting point for the workflow."""
|
||||||
|
|
||||||
|
@ -569,7 +569,11 @@ class MotionViewSet(ModelViewSet):
|
|||||||
person=request.user,
|
person=request.user,
|
||||||
skip_autoupdate=True,
|
skip_autoupdate=True,
|
||||||
)
|
)
|
||||||
inform_changed_data(motion, information=f"State set to {motion.state.name}.", user_id=request.user.pk)
|
inform_changed_data(
|
||||||
|
motion,
|
||||||
|
information=f"State set to {motion.state.name}.",
|
||||||
|
user_id=request.user.pk,
|
||||||
|
)
|
||||||
return Response({"detail": message})
|
return Response({"detail": message})
|
||||||
|
|
||||||
@list_route(methods=["post"])
|
@list_route(methods=["post"])
|
||||||
@ -1348,9 +1352,8 @@ class StateViewSet(
|
|||||||
Customized view endpoint to delete a state.
|
Customized view endpoint to delete a state.
|
||||||
"""
|
"""
|
||||||
state = self.get_object()
|
state = self.get_object()
|
||||||
if (
|
if state.workflow.first_state.pk == state.pk:
|
||||||
state.workflow.first_state.pk == state.pk
|
# is this the first state of the workflow?
|
||||||
): # is this the first state of the workflow?
|
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
{"detail": "You cannot delete the first state of the workflow."}
|
{"detail": "You cannot delete the first state of the workflow."}
|
||||||
)
|
)
|
||||||
|
@ -5,19 +5,23 @@ from django.db import migrations
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("users", "0008_user_gender")]
|
||||||
('users', '0008_user_gender'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='user',
|
name="user",
|
||||||
options={
|
options={
|
||||||
'default_permissions': (), 'ordering': ('last_name', 'first_name', 'username'),
|
"default_permissions": (),
|
||||||
'permissions': (
|
"ordering": ("last_name", "first_name", "username"),
|
||||||
('can_see_name', 'Can see names of users'),
|
"permissions": (
|
||||||
('can_see_extra_data', 'Can see extra data of users (e.g. present and comment)'),
|
("can_see_name", "Can see names of users"),
|
||||||
('can_change_password', 'Can change its own password'),
|
(
|
||||||
('can_manage', 'Can manage users'))},
|
"can_see_extra_data",
|
||||||
|
"Can see extra data of users (e.g. present and comment)",
|
||||||
),
|
),
|
||||||
|
("can_change_password", "Can change its own password"),
|
||||||
|
("can_manage", "Can manage users"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
22
openslides/users/migrations/0010_auto_20190119_1447.py
Normal file
22
openslides/users/migrations/0010_auto_20190119_1447.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-01-19 13:47
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("users", "0009_auto_20190119_0941")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="personalnote",
|
||||||
|
name="user",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
on_delete=openslides.utils.models.CASCADE_AND_AUTOUODATE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
@ -19,7 +19,7 @@ from jsonfield import JSONField
|
|||||||
|
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
from ..utils.auth import GROUP_ADMIN_PK
|
from ..utils.auth import GROUP_ADMIN_PK
|
||||||
from ..utils.models import RESTModelMixin
|
from ..utils.models import CASCADE_AND_AUTOUODATE, RESTModelMixin
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
GroupAccessPermissions,
|
GroupAccessPermissions,
|
||||||
PersonalNoteAccessPermissions,
|
PersonalNoteAccessPermissions,
|
||||||
@ -330,7 +330,7 @@ class PersonalNote(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
objects = PersonalNoteManager()
|
objects = PersonalNoteManager()
|
||||||
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, on_delete=CASCADE_AND_AUTOUODATE)
|
||||||
notes = JSONField()
|
notes = JSONField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -369,8 +369,6 @@ class GroupViewSet(ModelViewSet):
|
|||||||
id=full_data["id"],
|
id=full_data["id"],
|
||||||
collection_string=cachable.get_collection_string(),
|
collection_string=cachable.get_collection_string(),
|
||||||
full_data=full_data,
|
full_data=full_data,
|
||||||
information="",
|
|
||||||
user_id=None,
|
|
||||||
disable_history=True,
|
disable_history=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -571,7 +569,10 @@ class SetPasswordView(APIView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
user = request.user
|
user = request.user
|
||||||
if not (has_perm(user, "users.can_change_password") or has_perm(user, "users.can_manage")):
|
if not (
|
||||||
|
has_perm(user, "users.can_change_password")
|
||||||
|
or has_perm(user, "users.can_manage")
|
||||||
|
):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
if user.check_password(request.data["old_password"]):
|
if user.check_password(request.data["old_password"]):
|
||||||
try:
|
try:
|
||||||
@ -602,7 +603,10 @@ class PasswordResetView(APIView):
|
|||||||
"""
|
"""
|
||||||
Loop over all users and send emails.
|
Loop over all users and send emails.
|
||||||
"""
|
"""
|
||||||
if not (has_perm(request.user, "users.can_change_password") or has_perm(request.user, "users.can_manage")):
|
if not (
|
||||||
|
has_perm(request.user, "users.can_change_password")
|
||||||
|
or has_perm(request.user, "users.can_manage")
|
||||||
|
):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
to_email = request.data.get("email")
|
to_email = request.data.get("email")
|
||||||
for user in self.get_users(to_email):
|
for user in self.get_users(to_email):
|
||||||
@ -671,7 +675,10 @@ class PasswordResetConfirmView(APIView):
|
|||||||
http_method_names = ["post"]
|
http_method_names = ["post"]
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not (has_perm(request.user, "users.can_change_password") or has_perm(request.user, "users.can_manage")):
|
if not (
|
||||||
|
has_perm(request.user, "users.can_change_password")
|
||||||
|
or has_perm(request.user, "users.can_manage")
|
||||||
|
):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
uidb64 = request.data.get("user_id")
|
uidb64 = request.data.get("user_id")
|
||||||
token = request.data.get("token")
|
token = request.data.get("token")
|
||||||
|
@ -9,19 +9,33 @@ from mypy_extensions import TypedDict
|
|||||||
|
|
||||||
from .cache import element_cache, get_element_id
|
from .cache import element_cache, get_element_id
|
||||||
from .projector import get_projectot_data
|
from .projector import get_projectot_data
|
||||||
|
from .utils import get_model_from_collection_string
|
||||||
|
|
||||||
|
|
||||||
Element = TypedDict(
|
class ElementBase(TypedDict):
|
||||||
"Element",
|
id: int
|
||||||
{
|
collection_string: str
|
||||||
"id": int,
|
full_data: Optional[Dict[str, Any]]
|
||||||
"collection_string": str,
|
|
||||||
"full_data": Optional[Dict[str, Any]],
|
|
||||||
"information": str,
|
class Element(ElementBase, total=False):
|
||||||
"user_id": Optional[int],
|
"""
|
||||||
"disable_history": bool,
|
Data container to handle one root rest element for the autoupdate, history
|
||||||
},
|
and caching process.
|
||||||
)
|
|
||||||
|
The fields `id`, `collection_string` and `full_data` are required, the other
|
||||||
|
fields are optional.
|
||||||
|
|
||||||
|
if full_data is None, it means, that the element was deleted. If reload is
|
||||||
|
True, full_data is ignored and reloaded from the database later in the
|
||||||
|
process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
information: str
|
||||||
|
user_id: Optional[int]
|
||||||
|
disable_history: bool
|
||||||
|
reload: bool
|
||||||
|
|
||||||
|
|
||||||
AutoupdateFormat = TypedDict(
|
AutoupdateFormat = TypedDict(
|
||||||
"AutoupdateFormat",
|
"AutoupdateFormat",
|
||||||
@ -68,7 +82,6 @@ def inform_changed_data(
|
|||||||
full_data=root_instance.get_full_data(),
|
full_data=root_instance.get_full_data(),
|
||||||
information=information,
|
information=information,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
disable_history=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||||
@ -100,7 +113,6 @@ def inform_deleted_data(
|
|||||||
full_data=None,
|
full_data=None,
|
||||||
information=information,
|
information=information,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
disable_history=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||||
@ -201,6 +213,12 @@ def handle_changed_elements(elements: Iterable[Element]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if elements:
|
if elements:
|
||||||
|
for element in elements:
|
||||||
|
if element.get("reload"):
|
||||||
|
model = get_model_from_collection_string(element["collection_string"])
|
||||||
|
instance = model.objects.get(pk=element["id"])
|
||||||
|
element["full_data"] = instance.get_full_data()
|
||||||
|
|
||||||
# Save histroy here using sync code.
|
# Save histroy here using sync code.
|
||||||
history_instances = save_history(elements)
|
history_instances = save_history(elements)
|
||||||
|
|
||||||
@ -212,8 +230,6 @@ def handle_changed_elements(elements: Iterable[Element]) -> None:
|
|||||||
id=history_instance.get_rest_pk(),
|
id=history_instance.get_rest_pk(),
|
||||||
collection_string=history_instance.get_collection_string(),
|
collection_string=history_instance.get_collection_string(),
|
||||||
full_data=history_instance.get_full_data(),
|
full_data=history_instance.get_full_data(),
|
||||||
information="",
|
|
||||||
user_id=None,
|
|
||||||
disable_history=True, # This does not matter because history elements can never be part of the history itself.
|
disable_history=True, # This does not matter because history elements can never be part of the history itself.
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -227,9 +243,8 @@ def handle_changed_elements(elements: Iterable[Element]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def save_history(
|
def save_history(elements: Iterable[Element]) -> Iterable:
|
||||||
elements: Iterable[Element]
|
# TODO: Try to write Iterable[History] here
|
||||||
) -> Iterable: # TODO: Try to write Iterable[History] here
|
|
||||||
"""
|
"""
|
||||||
Thin wrapper around the call of history saving manager method.
|
Thin wrapper around the call of history saving manager method.
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from .access_permissions import BaseAccessPermissions
|
from .access_permissions import BaseAccessPermissions
|
||||||
|
from .autoupdate import Element, inform_changed_data, inform_changed_elements
|
||||||
from .rest_api import model_serializer_classes
|
from .rest_api import model_serializer_classes
|
||||||
from .utils import convert_camel_case_to_pseudo_snake_case
|
from .utils import convert_camel_case_to_pseudo_snake_case
|
||||||
|
|
||||||
@ -153,3 +154,44 @@ class RESTModelMixin:
|
|||||||
__import__(module_name)
|
__import__(module_name)
|
||||||
serializer_class = model_serializer_classes[type(self)]
|
serializer_class = model_serializer_classes[type(self)]
|
||||||
return serializer_class(self).data
|
return serializer_class(self).data
|
||||||
|
|
||||||
|
|
||||||
|
def SET_NULL_AND_AUTOUPDATE(
|
||||||
|
collector: Any, field: Any, sub_objs: Any, using: Any
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Like models.SET_NULL but also informs the autoupdate system about the
|
||||||
|
instance that was reference.
|
||||||
|
"""
|
||||||
|
if len(sub_objs) != 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
"SET_NULL_AND_AUTOUPDATE is used in an invalid usecase. Please report the bug!"
|
||||||
|
)
|
||||||
|
setattr(sub_objs[0], field.name, None)
|
||||||
|
inform_changed_data(sub_objs[0])
|
||||||
|
models.SET_NULL(collector, field, sub_objs, using)
|
||||||
|
|
||||||
|
|
||||||
|
def CASCADE_AND_AUTOUODATE(
|
||||||
|
collector: Any, field: Any, sub_objs: Any, using: Any
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Like models.SET_NULL but also informs the autoupdate system about the
|
||||||
|
root rest element of the also deleted instance.
|
||||||
|
"""
|
||||||
|
if len(sub_objs) != 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
"CASCADE_AND_AUTOUPDATE is used in an invalid usecase. Please report the bug!"
|
||||||
|
)
|
||||||
|
root_rest_element = sub_objs[0].get_root_rest_element()
|
||||||
|
inform_changed_elements(
|
||||||
|
[
|
||||||
|
Element(
|
||||||
|
collection_string=root_rest_element.get_collection_string(),
|
||||||
|
id=root_rest_element.pk,
|
||||||
|
full_data=None,
|
||||||
|
reload=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
models.CASCADE(collector, field, sub_objs, using)
|
||||||
|
@ -80,8 +80,6 @@ async def set_config():
|
|||||||
id=config_id,
|
id=config_id,
|
||||||
collection_string=collection_string,
|
collection_string=collection_string,
|
||||||
full_data=full_data,
|
full_data=full_data,
|
||||||
information="",
|
|
||||||
user_id=None,
|
|
||||||
disable_history=True,
|
disable_history=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user