django-restit 4.1.60__py3-none-any.whl → 4.1.62__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
account/rpc/group.py CHANGED
@@ -3,6 +3,7 @@ from rest import views as rv
3
3
  from account.models import Group, Membership, Member, GroupFeed, MemberFeed
4
4
  from taskqueue import models as tq
5
5
 
6
+
6
7
  @rd.url(r'^group$')
7
8
  @rd.url(r'^group/$') # required for legacy support
8
9
  @rd.url(r'^group/(?P<pk>\d+)$')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-restit
3
- Version: 4.1.60
3
+ Version: 4.1.62
4
4
  Summary: A Rest Framework for DJANGO
5
5
  License: MIT
6
6
  Author: Ian Starnes
@@ -33,7 +33,7 @@ account/periodic.py,sha256=imh9C-oQVhfw7GX3RRHYuC6DIreGbXrQM35A8C4UjOE,876
33
33
  account/rpc/__init__.py,sha256=L_AqHC0WbgUgLoqvNz6pY0E34eqh7sRaX77I6TxhRZ4,152
34
34
  account/rpc/auth.py,sha256=8CgXsbNZLeBcd5MHpdL29z9E8PYUcKX_zYpTPX_Pa-0,12516
35
35
  account/rpc/device.py,sha256=fbbZFp3cUdhVXvD7gVFOqFWj4hKS3bjZKD_aF5fQxd8,2852
36
- account/rpc/group.py,sha256=IHg0o4r0XcwsT8Ur5kTdjHc-91BNqk6atEeOpSd7cAc,3447
36
+ account/rpc/group.py,sha256=dUIZGArPgnKafL7F44f7Qum5EAskWcjj64Haqlkbdb8,3448
37
37
  account/rpc/member.py,sha256=oKdXSGhQ7AOPTwisZ5RvHhQ1SdZoXWlBQY0lIlDXJY0,1150
38
38
  account/rpc/notify.py,sha256=Q2YWejP36egeF060Hih5uX4Psv_B8NWlLLPi7iDYlIw,3344
39
39
  account/rpc/oauth.py,sha256=-BW38HjYwSQhOs31ubnBxO0yCKVpczEMoHq54NC9uOU,2610
@@ -89,20 +89,23 @@ incident/migrations/0006_delete_eventmetadata.py,sha256=buSEY_TmmXi9ShPFxB74Os2q
89
89
  incident/migrations/0007_event_metadata.py,sha256=syxcM4gIcBwSjcpdU69CXq5dGS1Gu8OodZtPJFDpY5U,414
90
90
  incident/migrations/0008_incident_action_sent_incident_hostname_and_more.py,sha256=N2D47E9k4PxOo-9C72gqYkb6K0GqVIO4PkvdnWlrSuA,903
91
91
  incident/migrations/0009_incident_reporter_ip.py,sha256=JOrTGBPJw9g7cg--HH3zqnbsMowjNSgzxJTrKojcPJA,473
92
+ incident/migrations/0010_incident_category_incident_component_id.py,sha256=FGRHnWJOPeKl5ko17L7rkERrLeyGCWET7sFmZbTB-YQ,631
93
+ incident/migrations/0011_ticket.py,sha256=Ml5E_Qi4Z0MD89fetoOFOL3rPlVQdjaaDCcFBfOuwd4,2142
92
94
  incident/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
- incident/models/__init__.py,sha256=adTSOSVrYJgHD4hZNJJjFj_M1QZbpwhZZQiIzanUKA8,167
94
- incident/models/event.py,sha256=TcnpP2b6EGOHC4FrGg0fEXYxR72ijpG15Da6QIYPd3Y,5664
95
- incident/models/incident.py,sha256=76dWWYgUDC8eg3DgrssAFnaXdIYybNcQzTu2R0nDbyI,12272
95
+ incident/models/__init__.py,sha256=NMphuhb0RTMf7Ov4QkNv7iv6_I8Wtr3xQ54yjX_a31M,209
96
+ incident/models/event.py,sha256=5cbtIQwY2DTBR9PkQX9jKIWtzbFb69BLMOUdWieMrVw,6772
97
+ incident/models/incident.py,sha256=xOtlGyc6TxOoCEtHU1bdSYFBnjfDGhzT2C0YFklfwmY,13059
96
98
  incident/models/ossec.py,sha256=pWMqcuTRxPFTEF-OZQSMn7YpNEE9mfsI4GMhWWjJs5I,2187
97
99
  incident/models/rules.py,sha256=67fbtEDfznxCYb-B89417lgYesN12PzmY25WBF_65a8,5300
100
+ incident/models/ticket.py,sha256=S3kqGQpYLE6Y4M9IKu_60sgW-f592xNr8uufqHnvDoU,2302
98
101
  incident/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
102
  incident/parsers/ossec.py,sha256=4_7Tpr_XBK8HerGSbdrOrAOAtbRvRE7WZ2Xedo3kp8g,5724
100
103
  incident/periodic.py,sha256=3YatKLLzoM9Q-3QXaIPtVIytLRCZt-7-IaD4tvKKGjg,723
101
- incident/rpc.py,sha256=kNUVrZDm_BoZQq3bVoOZJwMJX1nQJYLjkAy7EvPPLd4,5074
102
- incident/templates/email/incident_change.html,sha256=JMfaBmLRkWV3ef0ycG2GSzuRPwVivAqD59WNp8OOfwk,12732
104
+ incident/rpc.py,sha256=YkGXzE0AbrqRXlK9jZjE2VypT8mhcbVc49Yndr_c4BQ,5209
105
+ incident/templates/email/incident_change.html,sha256=O_5ocWTsnqmmOuQhAtXEiE4rWecnZnJjoKU4MwV6ILo,14178
103
106
  incident/templates/email/incident_new.html,sha256=IPX3CqIrvdrZSn13_jlR6sEb0If8ftvUrUpkzC5G2Gc,15173
104
- incident/templates/email/incident_plain.html,sha256=WlUwK0ataFPq-ZeIqzzMrrR51zSnxQ2WDhbJCa6hvRg,13208
105
- incident/tq.py,sha256=tpLE1ONEESwwnLrWD3VKDr7IT5hkKXNO61dVfG_wIWQ,3530
107
+ incident/templates/email/incident_plain.html,sha256=iTjdej6K9SO9Or5byX-0kc7c_BdSZWVgA301T03IyDk,14343
108
+ incident/tq.py,sha256=LYeOpHQ3sVEKtotZvbzscjB1Ror6HJtIceqate--Ibg,3820
106
109
  location/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
110
  location/admin.py,sha256=6S97Rlgjkk0jM15sbT1OJRPZbgvKn2rn7duCSazOXq4,297
108
111
  location/geolocate.py,sha256=UgV129vmSxnqYFBYJD2RQVOcC1-lJJ1zUaxDqOJRbG4,1694
@@ -347,7 +350,7 @@ pushit/utils.py,sha256=IeTCGa-164nmB1jIsK1lu1O1QzUhS3BKfuXHGjCW-ck,2121
347
350
  rest/.gitignore,sha256=TbEvWRMnAiajCTOdhiNrd9eeCAaIjRp9PRjE_VkMM5g,118
348
351
  rest/README.md,sha256=V3ETc-cJu8PZIbKr9xSe_pA4JEUpC8Dhw4bQeVCDJPw,5460
349
352
  rest/RemoteEvents.py,sha256=nL46U7AuxIrlw2JunphR1tsXyqi-ep_gD9CYGpYbNgE,72
350
- rest/__init__.py,sha256=VhkxGSIF7JGGKOBPMT5N5NSDQo2HsWa0Yit_DX4GpWI,121
353
+ rest/__init__.py,sha256=lB3bhQFmMA43cbx4GDWsoIIKZiruByPaff8EG57nMVM,121
351
354
  rest/arc4.py,sha256=y644IbF1ec--e4cUJ3KEYsewTCITK0gmlwa5mJruFC0,1967
352
355
  rest/cache.py,sha256=1Qg0rkaCJCaVP0-l5hZg2CIblTdeBSlj_0fP6vlKUpU,83
353
356
  rest/crypto/__init__.py,sha256=Tl0U11rgj1eBYqd6OXJ2_XSdNLumW_JkBZnaJqI6Ldw,72
@@ -478,7 +481,7 @@ ws4redis/servers/uwsgi.py,sha256=VyhoCI1DnVFqBiJYHoxqn5Idlf6uJPHvfBKgkjs34mo,172
478
481
  ws4redis/settings.py,sha256=K0yBiLUuY81iDM4Yr-k8hbvjn5VVHu5zQhmMK8Dtz0s,1536
479
482
  ws4redis/utf8validator.py,sha256=S0OlfjeGRP75aO6CzZsF4oTjRQAgR17OWE9rgZdMBZA,5122
480
483
  ws4redis/websocket.py,sha256=R0TUyPsoVRD7Y_oU7w2I6NL4fPwiz5Vl94-fUkZgLHA,14848
481
- django_restit-4.1.60.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
482
- django_restit-4.1.60.dist-info/METADATA,sha256=6F0LanvcKJFvJd0aYrpEr3WJuVjJjYUcUeq00Jv2FHw,7573
483
- django_restit-4.1.60.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
484
- django_restit-4.1.60.dist-info/RECORD,,
484
+ django_restit-4.1.62.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
485
+ django_restit-4.1.62.dist-info/METADATA,sha256=mdO3BNZQMo_z4xhhJdcVrlAaRCMaZ65wMh0uFwAEOv4,7573
486
+ django_restit-4.1.62.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
487
+ django_restit-4.1.62.dist-info/RECORD,,
@@ -0,0 +1,23 @@
1
+ # Generated by Django 4.1.4 on 2023-11-20 03:38
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('incident', '0009_incident_reporter_ip'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='incident',
15
+ name='category',
16
+ field=models.CharField(db_index=True, default=None, max_length=124, null=True),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='incident',
20
+ name='component_id',
21
+ field=models.IntegerField(blank=True, db_index=True, default=None, null=True),
22
+ ),
23
+ ]
@@ -0,0 +1,38 @@
1
+ # Generated by Django 4.1.4 on 2023-11-20 03:51
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+ import rest.models.base
6
+ import rest.models.metadata
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ('account', '0016_authsession_buid'),
13
+ ('incident', '0010_incident_category_incident_component_id'),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name='Ticket',
19
+ fields=[
20
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+ ('created', models.DateTimeField(auto_now_add=True)),
22
+ ('modified', models.DateTimeField(auto_now=True)),
23
+ ('category', models.CharField(db_index=True, max_length=200)),
24
+ ('priority', models.IntegerField(db_index=True, default=10)),
25
+ ('title', models.CharField(max_length=200)),
26
+ ('description', models.TextField()),
27
+ ('status', models.CharField(db_index=True, default=None, max_length=32, null=True)),
28
+ ('state', models.IntegerField(default=0)),
29
+ ('component', models.CharField(db_index=True, default=None, max_length=200, null=True)),
30
+ ('component_id', models.IntegerField(blank=True, db_index=True, default=None, null=True)),
31
+ ('assigned_to', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='account.member')),
32
+ ('created_by', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='account.member')),
33
+ ('group', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='account.group')),
34
+ ('incident', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='incident.incident')),
35
+ ],
36
+ bases=(models.Model, rest.models.base.RestModel, rest.models.metadata.MetaDataModel),
37
+ ),
38
+ ]
@@ -2,3 +2,4 @@ from .rules import * # noqa: F401, F403
2
2
  from .incident import * # noqa: F401, F403
3
3
  from .event import * # noqa: F401, F403
4
4
  from .ossec import * # noqa: F401, F403
5
+ from .ticket import * # noqa: F401, F403
incident/models/event.py CHANGED
@@ -15,7 +15,13 @@ from .rules import Rule
15
15
  INCIDENT_METRICS = settings.get("INCIDENT_METRICS", False)
16
16
  INCIDENT_EVENT_METRICS = settings.get("INCIDENT_EVENT_METRICS", False)
17
17
  EVENT_TO_INCIDENT_LEVEL = settings.get("EVENT_TO_INCIDENT_LEVEL", 4)
18
-
18
+ EVENT_DETAIL_TEMPLATES = settings.get("EVENT_DETAIL_TEMPLATES", None)
19
+ EVENT_META_KEYWORDS = settings.get("EVENT_META_KEYWORDS", [
20
+ "path", "ip", "reporter_ip", "code",
21
+ "reason", "buid", "merchant", "tid",
22
+ "group", "http_user_agent", "user_agent",
23
+ "app_url", "isp", "city", "state", "country"
24
+ ])
19
25
 
20
26
  logger = log.getLogger("incident", filename="incident.log")
21
27
 
@@ -80,6 +86,23 @@ class Event(JSONMetaData, rm.RestModel):
80
86
  return rule
81
87
  return None
82
88
 
89
+ @property
90
+ def details_by_category(self):
91
+ # returns detailed text based on the category settings
92
+ # if EVENT_DETAIL_TEMPLATES is None or self.category not in EVENT_DETAIL_TEMPLATES:
93
+ # return self.details
94
+ output = []
95
+ if self.component:
96
+ output.append(f"{self.component}({self.component_id})")
97
+ for key in EVENT_META_KEYWORDS:
98
+ if self.metadata.get(key, None) is not None:
99
+ output.append(f"{key}: {self.metadata[key]}")
100
+ if self.details:
101
+ output.append(self.details)
102
+ output.append("")
103
+ output.append("")
104
+ return "\n".join(output)
105
+
83
106
  def lookupIP(self, ip):
84
107
  GeoIP = rm.RestModel.getModel("location", "GeoIP")
85
108
  gip = GeoIP.lookup(ip)
@@ -138,7 +161,10 @@ class Event(JSONMetaData, rm.RestModel):
138
161
  incident = Incident(
139
162
  rule=hit_rule, priority=priority,
140
163
  reporter_ip=self.reporter_ip,
141
- component=self.category, hostname=self.hostname)
164
+ category=self.category,
165
+ component=self.component,
166
+ component_id=self.component_id,
167
+ hostname=self.hostname)
142
168
  if hit_rule is not None:
143
169
  incident.group = hit_rule.group
144
170
  # TODO possibly make this smarter?
@@ -52,7 +52,12 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
52
52
  modified = models.DateTimeField(auto_now=True)
53
53
 
54
54
  description = models.CharField(max_length=200)
55
+
56
+ category = models.CharField(max_length=124, null=True, default=None, db_index=True)
57
+
55
58
  component = models.CharField(max_length=200, null=True, default=None, db_index=True)
59
+ component_id = models.IntegerField(null=True, blank=True, default=None, db_index=True)
60
+
56
61
  hostname = models.CharField(max_length=200, null=True, default=None, db_index=True)
57
62
  reporter_ip = models.CharField(max_length=16, blank=True, null=True, default=None, db_index=True)
58
63
 
@@ -68,7 +73,15 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
68
73
  @property
69
74
  def first_event(self):
70
75
  return self.events.first()
71
-
76
+
77
+ def shouldTriggerAction(self):
78
+ count = self.events.all().count()
79
+ aa = self.rule.action_after
80
+ if aa>= 0:
81
+ return aa == count-1
82
+ aa = abs(self.rule.action_after)
83
+ return (count % aa) == 0
84
+
72
85
  def triggerAction(self, force=False):
73
86
  if self.rule is None:
74
87
  if self.action_sent is None:
@@ -82,7 +95,7 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
82
95
  # return
83
96
  # only do query if not 0
84
97
  logger.info("triggerAction", self.rule.action)
85
- if force or self.rule.action_after == 0 or self.rule.action_after < self.events.all().count():
98
+ if force or self.shouldTriggerAction():
86
99
  logger.info(f"triggering incident action: {self.rule.action}")
87
100
  self.triggerAsyncNotify()
88
101
  if self.rule.action is None or self.rule.action == "notify":
@@ -96,9 +109,12 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
96
109
 
97
110
  def triggerAsyncNotify(self):
98
111
  msg = dict(
112
+ pk=self.pk,
99
113
  created=time.mktime(self.created.timetuple()),
100
114
  description=self.description,
115
+ category=self.category,
101
116
  component=self.component,
117
+ component_id=self.component_id,
102
118
  hostname=self.hostname)
103
119
  if self.rule is not None:
104
120
  msg["rule"] = self.rule.name
@@ -148,7 +164,7 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
148
164
  # logger.info("notifyWith", perm)
149
165
  Member.notifyWith(
150
166
  perm,
151
- subject=F"Incident #{self.pk} {self.component}@{self.hostname}",
167
+ subject=F"New Incident #{self.pk} {self.component}@{self.hostname}",
152
168
  template=settings.get("INCIDENT_TEMPLATE", "email/incident_plain.html"),
153
169
  context=dict(incident=self, portal_url=settings.INCIDENT_PORTAL_URL),
154
170
  email_only=True, from_email=INCIDENT_EMAIL_FROM)
@@ -176,7 +192,8 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
176
192
  if request != None and "DATA" in request and "note" in request.DATA:
177
193
  self.logHistory(kind="note", note=request.DATA.get("note"), request=request)
178
194
 
179
- def logHistory(self, kind="history", note=None, media=None, request=None, member=None):
195
+ def logHistory(self, kind="history", note=None, media=None,
196
+ request=None, member=None, notify=True):
180
197
  if request is None:
181
198
  request = self.getActiveRequest()
182
199
  if member is None and request is not None and hasattr(request, "member"):
@@ -186,7 +203,7 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
186
203
  for k, v in self._changed__.items():
187
204
  nv = self.getFieldValue(k)
188
205
  notes.append(f"{k} changed from {v} to {nv}")
189
- note = "\n".join(notes)
206
+ note = "\n<br>".join(notes)
190
207
 
191
208
  h = IncidentHistory(
192
209
  parent=self,
@@ -203,57 +220,71 @@ class Incident(models.Model, rm.RestModel, rm.MetaDataModel):
203
220
  if media is not None:
204
221
  h.saveMediaFile(media, "media", media.name)
205
222
  h.save()
206
- if h.state != INCIDENT_STATE_IGNORE:
223
+ if notify and h.state != INCIDENT_STATE_IGNORE:
207
224
  self.notifyWatchers(
208
225
  subject=F"Updated Incident #{self.id}",
209
226
  history=h)
210
227
 
211
228
  def notifyWatchers(self, subject, history=None):
229
+ action = None
230
+ perm = "notify.incident_alerts"
231
+ if self.rule is not None and (self.rule.action.startswith("email:") or self.rule.action.startswith("notify:")):
232
+ action, perm = self.rule.action.split(":")
233
+ context = dict(
234
+ incident=self,
235
+ portal_url=settings.INCIDENT_PORTAL_URL,
236
+ history=history)
237
+ template = "email/incident_plain.html"
238
+
212
239
  # this should notify all users in our incident group of the change
213
240
  if self.group is not None:
214
241
  # all member of the group are notified because it is an incident group
215
242
  self.group.notifyMembers(
216
243
  subject=subject,
217
- template="email/incident_change.html",
218
- context=dict(incident=self, portal_url=settings.INCIDENT_PORTAL_URL),
219
- perms=["notify.incident_alerts"],
220
- email_only=True, from_email=INCIDENT_EMAIL_FROM)
221
- elif history is not None:
222
- action = None
223
- perm = "notify.incident_alerts"
224
- if self.rule is not None and (self.rule.action.startswith("email:") or self.rule.action.startswith("notify:")):
225
- action, perm = self.rule.action.split(":")
244
+ template=template,
245
+ context=context,
246
+ perms=[perm],
247
+ email_only=True,
248
+ from_email=INCIDENT_EMAIL_FROM)
249
+ elif history.by is None:
250
+ # notify everyone with the perm
251
+ Member.notifyWith(
252
+ perm,
253
+ subject,
254
+ template=template,
255
+ context=context,
256
+ email_only=True,
257
+ from_email=INCIDENT_EMAIL_FROM)
258
+ else:
259
+ # notitfy everyone but the sender
226
260
  if history.by is None:
227
- Member.notifyWith(
228
- perm, subject,
229
- message=history.note,
230
- from_email=INCIDENT_EMAIL_FROM)
231
- else:
232
261
  members = Member.GetWithPermission(perm).exclude(pk=history.by.pk)
233
- if members.count() > 0:
234
- NotificationRecord = Incident.getModel("account", "NotificationRecord")
235
- message = f"{history.note}\n\n- {history.by.username}\n\n\n"
236
- NotificationRecord.notify(
237
- members, subject, message,
238
- email_only=True, from_email=INCIDENT_EMAIL_FROM)
262
+ if members.count() == 0:
263
+ return
264
+ NotificationRecord = Incident.getModel("account", "NotificationRecord")
265
+ NotificationRecord.notify(
266
+ members,
267
+ subject,
268
+ template=template,
269
+ context=context,
270
+ email_only=True,
271
+ from_email=INCIDENT_EMAIL_FROM)
239
272
 
240
273
  @classmethod
241
274
  def getBundled(cls, rule, event):
242
275
  # calculate our bundle start time
243
276
  when = datetime.now() - timedelta(minutes=rule.bundle)
244
277
  q = objict(rule=rule, created__gte=when)
245
- if rule.bundle_by == 1:
246
- q.hostname = event.hostname
247
- elif rule.bundle_by == 2:
248
- q.component = event.category
249
- elif rule.bundle_by == 3:
278
+ if rule.bundle_by in [2, 3, 5]:
279
+ if event.component_id:
280
+ q.component = event.component
281
+ q.component_id = event.component_id
282
+ if rule.bundle_by in [1, 3, 7]:
250
283
  q.hostname = event.hostname
251
- q.component = event.category
252
- elif rule.bundle_by == 4:
253
- q.reporter_ip = event.reporter_ip
254
- elif rule.bundle_by == 5:
284
+ elif rule.bundle_by in [4, 5, 8]:
255
285
  q.reporter_ip = event.reporter_ip
256
- q.component = event.category
286
+ elif rule.bundle_by in [6, 7, 8]:
287
+ q.category = event.category
257
288
  return Incident.objects.filter(**q).last()
258
289
 
259
290
  @classmethod
@@ -0,0 +1,65 @@
1
+ from django.db import models
2
+ from django.conf import settings
3
+
4
+ from rest import models as rm
5
+ from rest import helpers as rh
6
+
7
+
8
+ class Ticket(models.Model, rm.RestModel, rm.MetaDataModel):
9
+ class RestMeta:
10
+ SEARCH_FIELDS = ["title", "description"]
11
+ CAN_DELETE = True
12
+ VIEW_PERMS = ["view_tickets"]
13
+ GRAPHS = {
14
+ "default": {
15
+ "extra": ["metadata"],
16
+ "graphs": {
17
+ "group": "basic",
18
+ "created_by": "basic",
19
+ "assigned_to": "basic",
20
+ "incident": "basic"
21
+ },
22
+ },
23
+ "detailed": {
24
+ "graphs": {
25
+ "group": "basic",
26
+ "created_by": "basic",
27
+ "assigned_to": "basic",
28
+ "incident": "basic",
29
+ "generic__component": "basic"
30
+ },
31
+ },
32
+ }
33
+
34
+ created = models.DateTimeField(auto_now_add=True)
35
+ modified = models.DateTimeField(auto_now=True)
36
+
37
+ group = models.ForeignKey(
38
+ "account.Group", on_delete=models.SET_NULL,
39
+ related_name="tickets",
40
+ null=True, default=None)
41
+ assigned_to = models.ForeignKey(
42
+ "account.Member", on_delete=models.SET_NULL,
43
+ related_name="tickets",
44
+ null=True, default=None)
45
+ created_by = models.ForeignKey(
46
+ "account.Member", on_delete=models.CASCADE,
47
+ related_name="+",
48
+ null=True, default=None)
49
+
50
+ # if category is null then this will run on all events?
51
+ category = models.CharField(max_length=200, db_index=True)
52
+ priority = models.IntegerField(default=10, db_index=True) # 1-10, 1 being the highest
53
+
54
+ title = models.CharField(max_length=200)
55
+ description = models.TextField()
56
+
57
+ status = models.CharField(max_length=32, default=None, null=True, db_index=True)
58
+ state = models.IntegerField(default=0) # how many incidents before firing action
59
+
60
+ incident = models.ForeignKey(
61
+ "incident.Incident", null=True, default=None,
62
+ related_name="tickets", on_delete=models.SET_NULL)
63
+
64
+ component = models.CharField(max_length=200, null=True, default=None, db_index=True)
65
+ component_id = models.IntegerField(null=True, blank=True, default=None, db_index=True)
incident/rpc.py CHANGED
@@ -158,3 +158,9 @@ def rest_firewall_block(request):
158
158
  return rv.restPermissionDenied(request)
159
159
  Task.Publish("incident", f"firewall_{action}", dict(ip=ip), channel="tq_broadcast")
160
160
  return rv.restStatus(request, True)
161
+
162
+
163
+ @rd.url('ticket')
164
+ @rd.url('ticket/<int:pk>')
165
+ def rest_on_ticket(request, pk=None):
166
+ return am.Ticket.on_rest_request(request, pk)