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.utils import to_roman
|
||||
|
||||
from ..utils.models import CASCADE_AND_AUTOUODATE, SET_NULL_AND_AUTOUPDATE
|
||||
from .access_permissions import ItemAccessPermissions
|
||||
|
||||
|
||||
@ -233,7 +234,7 @@ class Item(RESTModelMixin, models.Model):
|
||||
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
on_delete=models.SET_NULL,
|
||||
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="children",
|
||||
@ -374,7 +375,7 @@ class Speaker(RESTModelMixin, models.Model):
|
||||
|
||||
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.
|
||||
"""
|
||||
|
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.models import RESTModelMixin
|
||||
|
||||
from ..utils.models import CASCADE_AND_AUTOUODATE, SET_NULL_AND_AUTOUPDATE
|
||||
from .access_permissions import AssignmentAccessPermissions
|
||||
|
||||
|
||||
@ -36,7 +37,7 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
||||
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.
|
||||
"""
|
||||
@ -366,7 +367,9 @@ class AssignmentOption(RESTModelMixin, BaseOption):
|
||||
poll = models.ForeignKey(
|
||||
"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)
|
||||
|
||||
vote_class = AssignmentVote
|
||||
|
@ -5,14 +5,10 @@ from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0010_auto_20190118_1908'),
|
||||
]
|
||||
dependencies = [("core", "0010_auto_20190118_1908")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='history',
|
||||
name='now',
|
||||
field=models.DateTimeField(),
|
||||
),
|
||||
model_name="history", 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.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 (
|
||||
ChatMessageAccessPermissions,
|
||||
ConfigAccessPermissions,
|
||||
@ -108,7 +112,7 @@ class ProjectionDefault(RESTModelMixin, models.Model):
|
||||
display_name = models.CharField(max_length=256)
|
||||
|
||||
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):
|
||||
@ -179,7 +183,7 @@ class ChatMessage(RESTModelMixin, models.Model):
|
||||
|
||||
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:
|
||||
default_permissions = ()
|
||||
@ -269,7 +273,7 @@ class HistoryManager(models.Manager):
|
||||
history_time = now()
|
||||
for element in elements:
|
||||
if (
|
||||
element["disable_history"]
|
||||
element.get("disable_history")
|
||||
or element["collection_string"]
|
||||
== self.model.get_collection_string()
|
||||
):
|
||||
@ -282,8 +286,8 @@ class HistoryManager(models.Manager):
|
||||
element["collection_string"], element["id"]
|
||||
),
|
||||
now=history_time,
|
||||
information=element["information"],
|
||||
user_id=element["user_id"],
|
||||
information=element.get("information", ""),
|
||||
user_id=element.get("user_id"),
|
||||
full_data=data,
|
||||
)
|
||||
instance.save(
|
||||
@ -308,9 +312,6 @@ class HistoryManager(models.Manager):
|
||||
id=full_data["id"],
|
||||
collection_string=collection_string,
|
||||
full_data=full_data,
|
||||
information="",
|
||||
user_id=None,
|
||||
disable_history=False,
|
||||
)
|
||||
)
|
||||
instances = self.add_elements(elements)
|
||||
@ -336,7 +337,7 @@ class History(RESTModelMixin, models.Model):
|
||||
information = models.CharField(max_length=255)
|
||||
|
||||
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)
|
||||
|
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 ..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
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class Mediafile(RESTModelMixin, models.Model):
|
||||
"""A string representing the title of the file."""
|
||||
|
||||
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."""
|
||||
|
||||
|
@ -6,20 +6,20 @@ from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('motions', '0018_auto_20190118_2101'),
|
||||
]
|
||||
dependencies = [("motions", "0018_auto_20190118_2101")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='motion',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
model_name="motion",
|
||||
name="created",
|
||||
field=models.DateTimeField(
|
||||
auto_now_add=True, default=django.utils.timezone.now
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='motion',
|
||||
name='last_modified',
|
||||
model_name="motion",
|
||||
name="last_modified",
|
||||
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.models import RESTModelMixin
|
||||
|
||||
from ..utils.models import CASCADE_AND_AUTOUODATE, SET_NULL_AND_AUTOUPDATE
|
||||
from .access_permissions import (
|
||||
CategoryAccessPermissions,
|
||||
MotionAccessPermissions,
|
||||
@ -141,7 +142,7 @@ class Motion(RESTModelMixin, models.Model):
|
||||
"""
|
||||
|
||||
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.
|
||||
@ -171,7 +172,7 @@ class Motion(RESTModelMixin, models.Model):
|
||||
|
||||
sort_parent = models.ForeignKey(
|
||||
"self",
|
||||
on_delete=models.SET_NULL,
|
||||
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="children",
|
||||
@ -181,14 +182,14 @@ class Motion(RESTModelMixin, models.Model):
|
||||
"""
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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.
|
||||
@ -207,7 +208,7 @@ class Motion(RESTModelMixin, models.Model):
|
||||
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
on_delete=models.SET_NULL,
|
||||
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="amendments",
|
||||
@ -220,7 +221,7 @@ class Motion(RESTModelMixin, models.Model):
|
||||
|
||||
statute_paragraph = models.ForeignKey(
|
||||
StatuteParagraph,
|
||||
on_delete=models.SET_NULL,
|
||||
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="motions",
|
||||
@ -704,7 +705,7 @@ class Submitter(RESTModelMixin, models.Model):
|
||||
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.
|
||||
"""
|
||||
@ -754,7 +755,7 @@ class MotionChangeRecommendation(RESTModelMixin, models.Model):
|
||||
objects = MotionChangeRecommendationManager()
|
||||
|
||||
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."""
|
||||
|
||||
@ -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"""
|
||||
|
||||
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."""
|
||||
|
||||
@ -921,7 +922,7 @@ class MotionLog(RESTModelMixin, models.Model):
|
||||
"""
|
||||
|
||||
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."""
|
||||
|
||||
@ -1192,7 +1193,7 @@ class Workflow(RESTModelMixin, models.Model):
|
||||
"""A string representing the workflow."""
|
||||
|
||||
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."""
|
||||
|
||||
|
@ -569,7 +569,11 @@ class MotionViewSet(ModelViewSet):
|
||||
person=request.user,
|
||||
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})
|
||||
|
||||
@list_route(methods=["post"])
|
||||
@ -1348,9 +1352,8 @@ class StateViewSet(
|
||||
Customized view endpoint to delete a state.
|
||||
"""
|
||||
state = self.get_object()
|
||||
if (
|
||||
state.workflow.first_state.pk == state.pk
|
||||
): # is this the first state of the workflow?
|
||||
if state.workflow.first_state.pk == state.pk:
|
||||
# is this the first state of the workflow?
|
||||
raise ValidationError(
|
||||
{"detail": "You cannot delete the first state of the workflow."}
|
||||
)
|
||||
|
@ -5,19 +5,23 @@ from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0008_user_gender'),
|
||||
]
|
||||
dependencies = [("users", "0008_user_gender")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='user',
|
||||
name="user",
|
||||
options={
|
||||
'default_permissions': (), 'ordering': ('last_name', 'first_name', 'username'),
|
||||
'permissions': (
|
||||
('can_see_name', 'Can see names of 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'))},
|
||||
"default_permissions": (),
|
||||
"ordering": ("last_name", "first_name", "username"),
|
||||
"permissions": (
|
||||
("can_see_name", "Can see names of 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 ..utils.auth import GROUP_ADMIN_PK
|
||||
from ..utils.models import RESTModelMixin
|
||||
from ..utils.models import CASCADE_AND_AUTOUODATE, RESTModelMixin
|
||||
from .access_permissions import (
|
||||
GroupAccessPermissions,
|
||||
PersonalNoteAccessPermissions,
|
||||
@ -330,7 +330,7 @@ class PersonalNote(RESTModelMixin, models.Model):
|
||||
|
||||
objects = PersonalNoteManager()
|
||||
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
user = models.OneToOneField(User, on_delete=CASCADE_AND_AUTOUODATE)
|
||||
notes = JSONField()
|
||||
|
||||
class Meta:
|
||||
|
@ -369,8 +369,6 @@ class GroupViewSet(ModelViewSet):
|
||||
id=full_data["id"],
|
||||
collection_string=cachable.get_collection_string(),
|
||||
full_data=full_data,
|
||||
information="",
|
||||
user_id=None,
|
||||
disable_history=True,
|
||||
)
|
||||
)
|
||||
@ -571,7 +569,10 @@ class SetPasswordView(APIView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
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)
|
||||
if user.check_password(request.data["old_password"]):
|
||||
try:
|
||||
@ -602,7 +603,10 @@ class PasswordResetView(APIView):
|
||||
"""
|
||||
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)
|
||||
to_email = request.data.get("email")
|
||||
for user in self.get_users(to_email):
|
||||
@ -671,7 +675,10 @@ class PasswordResetConfirmView(APIView):
|
||||
http_method_names = ["post"]
|
||||
|
||||
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)
|
||||
uidb64 = request.data.get("user_id")
|
||||
token = request.data.get("token")
|
||||
|
@ -9,19 +9,33 @@ from mypy_extensions import TypedDict
|
||||
|
||||
from .cache import element_cache, get_element_id
|
||||
from .projector import get_projectot_data
|
||||
from .utils import get_model_from_collection_string
|
||||
|
||||
|
||||
Element = TypedDict(
|
||||
"Element",
|
||||
{
|
||||
"id": int,
|
||||
"collection_string": str,
|
||||
"full_data": Optional[Dict[str, Any]],
|
||||
"information": str,
|
||||
"user_id": Optional[int],
|
||||
"disable_history": bool,
|
||||
},
|
||||
)
|
||||
class ElementBase(TypedDict):
|
||||
id: int
|
||||
collection_string: str
|
||||
full_data: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Element(ElementBase, total=False):
|
||||
"""
|
||||
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",
|
||||
@ -68,7 +82,6 @@ def inform_changed_data(
|
||||
full_data=root_instance.get_full_data(),
|
||||
information=information,
|
||||
user_id=user_id,
|
||||
disable_history=False,
|
||||
)
|
||||
|
||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||
@ -100,7 +113,6 @@ def inform_deleted_data(
|
||||
full_data=None,
|
||||
information=information,
|
||||
user_id=user_id,
|
||||
disable_history=False,
|
||||
)
|
||||
|
||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||
@ -201,6 +213,12 @@ def handle_changed_elements(elements: Iterable[Element]) -> None:
|
||||
)
|
||||
|
||||
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.
|
||||
history_instances = save_history(elements)
|
||||
|
||||
@ -212,8 +230,6 @@ def handle_changed_elements(elements: Iterable[Element]) -> None:
|
||||
id=history_instance.get_rest_pk(),
|
||||
collection_string=history_instance.get_collection_string(),
|
||||
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.
|
||||
)
|
||||
)
|
||||
@ -227,9 +243,8 @@ def handle_changed_elements(elements: Iterable[Element]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def save_history(
|
||||
elements: Iterable[Element]
|
||||
) -> Iterable: # TODO: Try to write Iterable[History] here
|
||||
def save_history(elements: Iterable[Element]) -> Iterable:
|
||||
# TODO: Try to write Iterable[History] here
|
||||
"""
|
||||
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 .access_permissions import BaseAccessPermissions
|
||||
from .autoupdate import Element, inform_changed_data, inform_changed_elements
|
||||
from .rest_api import model_serializer_classes
|
||||
from .utils import convert_camel_case_to_pseudo_snake_case
|
||||
|
||||
@ -153,3 +154,44 @@ class RESTModelMixin:
|
||||
__import__(module_name)
|
||||
serializer_class = model_serializer_classes[type(self)]
|
||||
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,
|
||||
collection_string=collection_string,
|
||||
full_data=full_data,
|
||||
information="",
|
||||
user_id=None,
|
||||
disable_history=True,
|
||||
)
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user