-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathviews.py
291 lines (237 loc) · 11.5 KB
/
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
from copy import deepcopy
from django.db.models import Q
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
import custom_view
from airone.lib.acl import ACLType
from airone.lib.types import AttrTypeValue
from entity.models import Entity
from entry.models import Entry
from entry.settings import CONFIG as ENTRY_CONFIG
from job.models import Job
from .serializers import PostEntrySerializer
class EntryAPI(APIView):
def _be_compatible_with_trigger(self, entry, request_params):
entry_dict = entry.to_dict(self.request.user, with_metainfo=True)
def _get_value(attrname, attrtype, value):
if isinstance(value, list):
return [_get_value(attrname, attrtype, x) for x in value]
elif attrtype & AttrTypeValue["named"]:
[co_value] = list(value.values())
return co_value["id"] if co_value else None
elif attrtype & AttrTypeValue["object"]:
return value["id"] if value else None
else:
return value
trigger_params = []
for attrname in request_params["attrs"].keys():
try:
[(entity_attr_id, attrtype, attrvalue)] = [
(x["schema_id"], x["value"]["type"], x["value"]["value"])
for x in entry_dict["attrs"]
if x["name"] == attrname
]
except ValueError:
continue
trigger_params.append(
{"id": entity_attr_id, "value": _get_value(attrname, attrtype, attrvalue)}
)
return trigger_params
def post(self, request, format=None):
sel = PostEntrySerializer(data=request.data)
# This is necessary because request.data might be changed by the processing of serializer
raw_request_data = deepcopy(request.data)
if not sel.is_valid():
ret = {
"result": "Validation Error",
"details": ["(%s) %s" % (k, ",".join(e)) for k, e in sel._errors.items()],
}
return Response(ret, status=status.HTTP_400_BAD_REQUEST)
# checking that target user has permission to create an entry
if not request.user.has_permission(sel.validated_data["entity"], ACLType.Writable):
return Response(
{"result": "Permission denied to create(or update) entry"},
status=status.HTTP_400_BAD_REQUEST,
)
# set target entry information to response data
resp_data = {
"updated_attrs": {}, # This describes updated attribute values
"is_created": False, # This sets true when target entry will be created in this
# processing
}
# This variable indicates whether NOTIFY_UPDATE_ENTRY Job will be created.
# This is necessary to create minimum necessary NOTIFY_UPDATE_ENTRY Job.
will_notify_update_entry = False
# Common processing to update Entry's name and set will_notify_update_entry variable
def _update_entry_name(entry):
# Set Entry status that indicates target Entry is under editing processing
# to prevent to updating this entry from others.
entry.set_status(Entry.STATUS_EDITING)
# Set will_notify_update_entry when name parameter is different with target Entry's name
_will_notify_update_entry = False
if entry.name != sel.validated_data["name"]:
entry.name = sel.validated_data["name"]
entry.save(update_fields=["name"])
_will_notify_update_entry = True
return _will_notify_update_entry
entry_condition = {
"schema": sel.validated_data["entity"],
"name": sel.validated_data["name"],
"is_active": True,
}
if "id" in sel.validated_data:
# prevent to register duplicate entry-name with other entry
if Entry.objects.filter(
Q(**entry_condition) & ~Q(id=sel.validated_data["id"])
).exists():
return Response(
{"result": '"%s" is duplicate name with other Entry' % entry_condition["name"]},
status=status.HTTP_400_BAD_REQUEST,
)
entry = Entry.objects.get(id=sel.validated_data["id"])
if not request.user.has_permission(entry, ACLType.Writable):
return Response(
{"result": "Permission denied to update entry"},
status=status.HTTP_400_BAD_REQUEST,
)
will_notify_update_entry = _update_entry_name(entry)
elif Entry.objects.filter(**entry_condition).exists():
entry = Entry.objects.get(**entry_condition)
if not request.user.has_permission(entry, ACLType.Writable):
return Response(
{"result": "Permission denied to update entry"},
status=status.HTTP_400_BAD_REQUEST,
)
will_notify_update_entry = _update_entry_name(entry)
else:
# This is the processing just in case for safety not to create duplicate Entries
# when multiple requests passed through existance check. Even through multiple
# requests coming here, Django prevents from creating multiple Entries.
entry, is_created = Entry.objects.update_or_create(
created_user=request.user, status=Entry.STATUS_CREATING, **entry_condition
)
resp_data["is_created"] = True
if is_created:
# create job to notify entry event to the registered WebHook
Job.new_notify_create_entry(request.user, entry).run()
else:
# set flag to create a Job of NOTIFY_UPDATE_ENTRY in later
# (Note: This code is rarely run!)
will_notify_update_entry = True
entry.complement_attrs(request.user)
for name, value in sel.validated_data["attrs"].items():
# If user doesn't have readable permission for target Attribute, it won't be created.
if not entry.attrs.filter(name=name).exists():
continue
attr = entry.attrs.get(schema__name=name, is_active=True)
if request.user.has_permission(attr.schema, ACLType.Writable) and attr.is_updated(
value
):
attr.add_value(request.user, value)
will_notify_update_entry = True
# This enables to let user know what attributes are changed in this request
resp_data["updated_attrs"][name] = raw_request_data["attrs"][name]
if will_notify_update_entry:
# Create job to notify event, which indicates target entry is updated,
# to the registered WebHook.
Job.new_notify_update_entry(request.user, entry).run()
# register target Entry to the Elasticsearch
entry.register_es()
# Create job for TriggerAction. Before calling, it's necessary to make parameters to pass
# to TriggerAction from raw_request_data by _be_compatible_with_trigger() method.
Job.new_invoke_trigger(
request.user, entry, self._be_compatible_with_trigger(entry, raw_request_data)
).run()
entry.del_status(Entry.STATUS_CREATING | Entry.STATUS_EDITING)
return Response(dict({"result": entry.id}, **resp_data))
def get(self, request, *args, **kwargs):
# The parameter for entry is acceptable both id and name.
param_entry_id = request.GET.get("entry_id")
param_entry_name = request.GET.get("entry")
param_entity = request.GET.get("entity")
param_offset = request.GET.get("offset", "0")
if not (param_entry_name or param_entry_id or param_entity):
return Response(
{"result": 'Parameter any of "entry", "entry_id" or "entity" is mandatory'},
status=status.HTTP_400_BAD_REQUEST,
)
if param_entry_id and not param_entry_id.isdigit():
return Response(
{"result": 'Parameter "entry_id" is numerically'},
status=status.HTTP_400_BAD_REQUEST,
)
if not param_offset.isdigit():
return Response(
{"result": 'Parameter "offset" is numerically'},
status=status.HTTP_400_BAD_REQUEST,
)
entity = None
if param_entity:
entity = Entity.objects.filter(name=param_entity).first()
if not entity:
return Response(
{"result": "Failed to find specified Entity (%s)" % param_entity},
status=status.HTTP_404_NOT_FOUND,
)
# This enables to return deleted values
is_active = request.GET.get("is_active", True)
# make a query based on GET parameters
query = Q(is_active=is_active)
if entity:
query = Q(query, schema=entity)
if param_entry_id:
query = Q(query, id=param_entry_id)
elif param_entry_name:
query = Q(query, name=param_entry_name)
retinfo = [
x.to_dict(request.user)
for x in Entry.objects.filter(query)[
int(param_offset) : int(param_offset) + ENTRY_CONFIG.MAX_LIST_ENTRIES
]
]
if not any(retinfo):
return Response({"result": "Failed to find entry"}, status=status.HTTP_404_NOT_FOUND)
return Response([x for x in retinfo if x])
def delete(self, request, *args, **kwargs):
# checks mandatory parameters are specified
if not all([x in request.data for x in ["entity", "entry"]]):
return Response(
'Parameter "entity" and "entry" are mandatory',
status=status.HTTP_400_BAD_REQUEST,
)
entity = Entity.objects.filter(name=request.data["entity"]).first()
if not entity:
return Response(
"Failed to find specified Entity (%s)" % request.data["entity"],
status=status.HTTP_404_NOT_FOUND,
)
entry = Entry.objects.filter(name=request.data["entry"], schema=entity).first()
if not entry:
return Response(
"Failed to find specified Entry (%s)" % request.data["entry"],
status=status.HTTP_404_NOT_FOUND,
)
# permission check
if not request.user.has_permission(entry, ACLType.Writable):
return Response("Permission denied to operate", status=status.HTTP_400_BAD_REQUEST)
if custom_view.is_custom("delete_entry_api", entry.schema.name):
# do_delete custom view
resp = custom_view.call_custom(
"delete_entry_api", entry.schema.name, request.user, entry
)
# If custom_view returns available response this returns it to user,
# or continues default processing.
if resp:
return resp
# Delete the specified entry then return its id, if is active
if entry.is_active:
# create a new Job to delete entry and run it
job = Job.new_delete(request.user, entry)
# create and run notify delete entry task
job_notify = Job.new_notify_delete_entry(request.user, entry)
job_notify.run()
job.dependent_job = job_notify
job.save(update_fields=["dependent_job"])
job.run()
return Response({"id": entry.id})