def edit_entry_attrs(self, job_id): job = Job.objects.get(id=job_id) if job.proceed_if_ready(): # At the first time, update job status to prevent executing this job duplicately job.update(Job.STATUS["PROCESSING"]) user = User.objects.get(id=job.user.id) entry = Entry.objects.get(id=job.target.id) recv_data = json.loads(job.params) for info in recv_data["attrs"]: if info["id"]: attr = Attribute.objects.get(id=info["id"]) else: entity_attr = EntityAttr.objects.get(id=info["entity_attr_id"]) attr = entry.attrs.filter(schema=entity_attr, is_active=True).first() if not attr: attr = entry.add_attribute_from_base(entity_attr, user) try: converted_value = _convert_data_value(attr, info) except ValueError as e: Logger.warning("(%s) attr_data: %s" % (e, str(info))) continue # Check a new update value is specified, or not if not attr.is_updated(converted_value): continue # Add new AttributeValue instance to Attribute instnace attr.add_value(user, converted_value) if custom_view.is_custom("after_edit_entry", entry.schema.name): custom_view.call_custom("after_edit_entry", entry.schema.name, recv_data, user, entry) # update entry information to Elasticsearch entry.register_es() # clear flag to specify this entry has been completed to edit entry.del_status(Entry.STATUS_EDITING) # update job status and save it job.update(Job.STATUS["DONE"]) # running job to notify changing entry event job_notify_event = Job.new_notify_update_entry(user, entry) job_notify_event.run()
def post(self, request, format=None): user = User.objects.get(id=request.user.id) 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 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 } 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']) entry.name = sel.validated_data['name'] entry.save(update_fields=['name']) entry.set_status(Entry.STATUS_EDITING) # create job to notify entry event to the registered WebHook job_notify = Job.new_notify_update_entry(user, entry) elif Entry.objects.filter(**entry_condition).exists(): entry = Entry.objects.get(**entry_condition) entry.set_status(Entry.STATUS_EDITING) # create job to notify entry event to the registered WebHook job_notify = Job.new_notify_update_entry(user, entry) else: entry = Entry.objects.create(created_user=user, status=Entry.STATUS_CREATING, **entry_condition) resp_data['is_created'] = True # create job to notify entry event to the registered WebHook job_notify = Job.new_notify_create_entry(user, entry) entry.complement_attrs(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 user.has_permission( attr.schema, ACLType.Writable) and attr.is_updated(value): attr.add_value(user, value) # This enables to let user know what attributes are changed in this request resp_data['updated_attrs'][name] = raw_request_data['attrs'][ name] # register target Entry to the Elasticsearch entry.register_es() # run notification job job_notify.run() entry.del_status(Entry.STATUS_CREATING | Entry.STATUS_EDITING) return Response(dict({'result': entry.id}, **resp_data))
def revert_attrv(request, recv_data): attr = Attribute.objects.filter(id=recv_data["attr_id"]).first() if not attr: return HttpResponse("Specified Attribute-id is invalid", status=400) if not request.user.has_permission(attr, ACLType.Writable): return HttpResponse( "You don't have permission to update this Attribute", status=400) attrv = AttributeValue.objects.filter(id=recv_data["attrv_id"]).first() if not attrv or attrv.parent_attr.id != attr.id: return HttpResponse("Specified AttributeValue-id is invalid", status=400) # When the AttributeType was changed after settting value, this operation is aborted if attrv.data_type != attr.schema.type: return HttpResponse( "Attribute-type was changed after this value was registered.", status=400) latest_value = attr.get_latest_value() if latest_value.get_value() != attrv.get_value(): # copy specified AttributeValue new_attrv = AttributeValue.objects.create( **{ "value": attrv.value, "referral": attrv.referral, "status": attrv.status, "boolean": attrv.boolean, "date": attrv.date, "data_type": attrv.data_type, "created_user": request.user, "parent_attr": attr, "is_latest": True, }) # This also copies child attribute values and append new one new_attrv.data_array.add(*[ AttributeValue.objects.create( **{ "value": v.value, "referral": v.referral, "created_user": request.user, "parent_attr": attr, "status": v.status, "boolean": v.boolean, "date": v.date, "data_type": v.data_type, "is_latest": False, "parent_attrv": new_attrv, }) for v in attrv.data_array.all() ]) # append cloned value to Attribute attr.values.add(new_attrv) # clear all exsts latest flag attr.unset_latest_flag(exclude_id=new_attrv.id) # register update to the Elasticsearch attr.parent_entry.register_es() # Send notification to the webhook URL job_notify = Job.new_notify_update_entry(request.user, attr.parent_entry) job_notify.run() # call custom-view if it exists if custom_view.is_custom("revert_attrv", attr.parent_entry.schema.name): return custom_view.call_custom(*[ "revert_attrv", attr.parent_entry.schema.name, request, request.user, attr, latest_value, new_attrv, ]) return HttpResponse('Succeed in updating Attribute "%s"' % attr.schema.name)
def _do_import_entries(job): user = job.user entity = Entity.objects.get(id=job.target.id) if not user.has_permission(entity, ACLType.Writable): job.update( **{ 'status': Job.STATUS['ERROR'], 'text': 'Permission denied to import. ' 'You need Writable permission for "%s"' % entity.name }) return whole_data = json.loads(job.params).get(entity.name) if not whole_data: job.update( **{ 'status': Job.STATUS['ERROR'], 'text': 'Uploaded file has no entry data of %s' % entity.name }) return # get custom_view method to prevent executing check method in every loop processing custom_view_handler = None if custom_view.is_custom("after_import_entry", entity.name): custom_view_handler = 'after_import_entry' job.update(Job.STATUS['PROCESSING']) total_count = len(whole_data) # create or update entry for (index, entry_data) in enumerate(whole_data): job.text = 'Now importing... (progress: [%5d/%5d])' % (index + 1, total_count) job.save(update_fields=['text']) # abort processing when job is canceled if job.is_canceled(): return entry = Entry.objects.filter(name=entry_data['name'], schema=entity).first() if not entry: entry = Entry.objects.create(name=entry_data['name'], schema=entity, created_user=user) # create job to notify create event to the WebHook URL job_notify = Job.new_notify_create_entry(user, entry) elif not user.has_permission(entry, ACLType.Writable): continue else: # create job to notify edit event to the WebHook URL job_notify = Job.new_notify_update_entry(user, entry) entry.complement_attrs(user) for attr_name, value in entry_data['attrs'].items(): # If user doesn't have readable permission for target Attribute, # it won't be created. if not entry.attrs.filter(schema__name=attr_name).exists(): continue # There should be only one EntityAttr that is specified by name and Entity. # Once there are multiple EntityAttrs, it must be an abnormal situation. # In that case, this aborts import processing for this entry and reports it # as an error. attr_query = entry.attrs.filter(schema__name=attr_name, is_active=True, schema__parent_entity=entry.schema) if attr_query.count() > 1: Logger.error( '[task.import_entry] Abnormal entry was detected(%s:%d)' % (entry.name, entry.id)) break attr = attr_query.last() if (not user.has_permission(attr.schema, ACLType.Writable) or not user.has_permission(attr, ACLType.Writable)): continue input_value = attr.convert_value_to_register(value) if user.has_permission( attr.schema, ACLType.Writable) and attr.is_updated(input_value): attr.add_value(user, input_value) # call custom-view processing corresponding to import entry if custom_view_handler: custom_view.call_custom(custom_view_handler, entity.name, user, entry, attr, value) # register entry to the Elasticsearch entry.register_es() # run notification job job_notify.run() if not job.is_canceled(): job.update(status=Job.STATUS['DONE'], text='')
def update(self, entry: Entry, validated_data): entry.set_status(Entry.STATUS_EDITING) user: User = self.context["request"].user entity_name = entry.schema.name if custom_view.is_custom("before_update_entry", entity_name): custom_view.call_custom("before_update_entry", entity_name, user, validated_data, entry) attrs_data = validated_data.pop("attrs", []) # update name of Entry object. If name would be updated, the elasticsearch data of entries # that refers this entry also be updated by creating REGISTERED_REFERRALS task. job_register_referrals: Optional[Job] = None if "name" in validated_data and entry.name != validated_data["name"]: entry.name = validated_data["name"] entry.save(update_fields=["name"]) job_register_referrals = Job.new_register_referrals(user, entry) for entity_attr in entry.schema.attrs.filter(is_active=True): attr: Attribute = entry.attrs.filter(schema=entity_attr, is_active=True).first() if not attr: attr = entry.add_attribute_from_base(entity_attr, user) # skip for unpermitted attributes if not user.has_permission(attr, ACLType.Writable): continue # make AttributeValue object if the value is specified attr_data = [ x for x in attrs_data if int(x["id"]) == entity_attr.id ] if not attr_data: continue # Check a new update value is specified, or not if not attr.is_updated(attr_data[0]["value"]): continue attr.add_value(user, attr_data[0]["value"]) if custom_view.is_custom("after_update_entry", entity_name): custom_view.call_custom("after_update_entry", entity_name, user, attrs_data, entry) # update entry information to Elasticsearch entry.register_es() # clear flag to specify this entry has been completed to edit entry.del_status(Entry.STATUS_EDITING) # running job of re-register referrals because of chaning entry's name if job_register_referrals: job_register_referrals.run() # running job to notify changing entry event job_notify_event: Job = Job.new_notify_update_entry(user, entry) job_notify_event.run() return entry
def test_may_schedule_with_parallelizable_operation(self): [job1, job2] = [Job.new_notify_update_entry(self.guest, self.entry) for _ in range(2)] self.assertEqual(job2.dependent_job, job1) self.assertEqual(job1.status, Job.STATUS["PREPARING"]) self.assertTrue(job2.proceed_if_ready())
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"]) will_notify_update_entry = _update_entry_name(entry) elif Entry.objects.filter(**entry_condition).exists(): entry = Entry.objects.get(**entry_condition) will_notify_update_entry = _update_entry_name(entry) else: entry = Entry.objects.create(created_user=request.user, status=Entry.STATUS_CREATING, **entry_condition) resp_data["is_created"] = True # create job to notify entry event to the registered WebHook Job.new_notify_create_entry(request.user, entry).run() 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() entry.del_status(Entry.STATUS_CREATING | Entry.STATUS_EDITING) return Response(dict({"result": entry.id}, **resp_data))