django-restit 4.2.176__py3-none-any.whl → 4.2.178__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/models/group.py CHANGED
@@ -52,6 +52,7 @@ class Group(models.Model, RestModel, MetaDataModel):
52
52
  LIST_PARENT_KINDS = ["org", "iso"]
53
53
  POST_SAVE_FIELDS = ["child_of"]
54
54
  GROUP_FIELD = "self"
55
+ CAN_BATCH = True
55
56
  VIEW_PERMS = [
56
57
  "view_all_groups",
57
58
  "manage_groups",
account/models/member.py CHANGED
@@ -72,6 +72,7 @@ class Member(User, RestModel, MetaDataModel):
72
72
  SEARCH_FIELDS = ["username", "email", "first_name", "last_name", "display_name", "phone_number"]
73
73
  VIEW_PERMS = ["view_members", "manage_members", "manage_users", "owner"]
74
74
  SAVE_PERMS = ["invite_members", "manage_members", "manage_users", "owner"]
75
+ CAN_BATCH = True
75
76
  LIST_DEFAULT_FILTERS = {
76
77
  "is_active": True
77
78
  }
@@ -988,7 +989,12 @@ class Member(User, RestModel, MetaDataModel):
988
989
  request.member.log("permission_denied", "attempting to set password for user: {}".format(self.username), method="password_change", level=3)
989
990
  raise PermissionDeniedException("Permission Denied: attempting to change password")
990
991
  if request.member.id != self.id:
991
- self.log("modified_by", "password changed by: {}".format(request.member.username), method="password_change", level=31)
992
+ error = f"{self.username} password changed by: {request.member.username}"
993
+ self.reportIncident("account", error, details=error, error_code=497)
994
+ self.log("modified_by", error, method="password_change", level=31)
995
+
996
+ error = f"{self.username} password changed by: {request.member.username}"
997
+ request.member.reportIncident("account", error, details=error, error_code=497)
992
998
  request.member.log("member_edit", "{} password changed".format(self.username), method="password_change", level=31)
993
999
  self.setPassword(value, skip_history=True)
994
1000
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-restit
3
- Version: 4.2.176
3
+ Version: 4.2.178
4
4
  Summary: A Rest Framework for DJANGO
5
5
  License: MIT
6
6
  Author: Ian Starnes
@@ -29,9 +29,9 @@ account/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
29
29
  account/models/__init__.py,sha256=cV_lMnT2vL_mjiYtT4hlcIHo52ocFbGSNVkOIHHLXZY,385
30
30
  account/models/device.py,sha256=8D-Sbv9PZWAnX6UVpp1lNJ03P24fknNnN1VOhqY7RVg,6306
31
31
  account/models/feeds.py,sha256=vI7fG4ASY1M0Zjke24RdnfDcuWeATl_yR_25jPmT64g,2011
32
- account/models/group.py,sha256=JVyMIakLskUuGXBJFyessw4LlD9Fl6AsHtpo1yZEYjk,22884
32
+ account/models/group.py,sha256=Pmm6G5Qb9C-OQ70xUxp5M1_PAg1DtvUxb5WFFW-WmOY,22909
33
33
  account/models/legacy.py,sha256=zYdtv4LC0ooxPVqWM-uToPwV-lYWQLorSE6p6yn1xDw,2720
34
- account/models/member.py,sha256=6-KbOYFyvloyiITgz5gKMyORmPtDESdzbBbMrjmz4Rs,55058
34
+ account/models/member.py,sha256=xlX0yqE3yFwPaZkcdU4e3g3yTommHimsLG8wBp09x90,55376
35
35
  account/models/membership.py,sha256=90EpAhOsGaqphDAkONP6j_qQ0OWSRaQsI8H7E7fgMkE,9249
36
36
  account/models/notify.py,sha256=YKYEXT56i98b7-ydLt5UuEVOqW7lipQMi-KuiPhcSwY,15627
37
37
  account/models/passkeys.py,sha256=lObapudvL--ABSTZTIELmYvHE3dPF0tO_KmuYk0ZJXc,1699
@@ -93,7 +93,7 @@ inbox/models/template.py,sha256=i5vf0vsM1U0251UmVsF61MDCV_c7xt-zdCdx1SiKOG0,1013
93
93
  inbox/rpc.py,sha256=7JXvpXlEGKG7by_NkANPGYLCzagyCnTIGM4rme_htpk,1534
94
94
  inbox/utils/__init__.py,sha256=P_UR2rGK3L0tZNlTN-mf99tpeYM-tLkA18iDKXSSLDM,89
95
95
  inbox/utils/parsing.py,sha256=y_71dwz8bm3JvF35ol8698XJ36sBF8fQWUrn0sYd2Fs,5597
96
- inbox/utils/render.py,sha256=CU_F2qUBQE7mjb9Q6Dn9ro5CS_O_zEY-wDMHEClKkIA,4331
96
+ inbox/utils/render.py,sha256=Jk_YYY6uztURh0qQfDDZYpOj9awvqkqkAaBmwJIWivU,4543
97
97
  inbox/utils/sending.py,sha256=BKelTZnbkdSLGpjOY6IRTrzj-Hnw2pPZ7RYQGwe-tqk,2179
98
98
  incident/README.md,sha256=4vbZTJj7uUmq8rogYngxqNYjFTlBOujfWUGheLoFKMc,1114
99
99
  incident/__init__.py,sha256=FXNMmcGP6YAKjwik84ppze33uL0kDTa7YFr3aOEXhhk,3658
@@ -177,7 +177,7 @@ medialib/forms.py,sha256=nrE6QTPNPiIeX7Nx4l9DEmAQeQXqFyCg1C3JEDBYJfE,5442
177
177
  medialib/migrations/0001_initial.py,sha256=H3JliH5aw7tiHef8MhrJr_9rGetqgA7UjTF-eKziRSM,20518
178
178
  medialib/migrations/0002_alter_mediaitemrendition_bytes.py,sha256=igC1R02smbNoWlk2T4uCi9cNilOsxGKD-D24fQv92dM,414
179
179
  medialib/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
180
- medialib/models.py,sha256=fsgNxBtJNR7lufbWUsI_LtxfYmLBTpIQ0uOW-T_CsD8,55032
180
+ medialib/models.py,sha256=V-ouyUVgPKDIVvzGyqSoMWdhSBV8ZXHQKLXEyOTLAx0,54976
181
181
  medialib/ocr.py,sha256=zlP7-NBiXhW7jR9pljmEPl5xzLVZpLN5QLAELQgU0Fk,1189
182
182
  medialib/pdf.py,sha256=l28WwM0JKbT9boV-b_9TFh9jhvGcrquR8GqC8wfEaLk,1275
183
183
  medialib/qrcode.py,sha256=vHyA5egXOX70EFiUDgr1njI9zcF6bXQJ_hKAQrppRow,545
@@ -339,7 +339,7 @@ medialib/youtube/upload.py,sha256=MTuPxm1ZC-y5pXAGtLNtp1hBSNZgCKYt1ewP5hwMQHI,28
339
339
  medialib/youtube/uritemplate/__init__.py,sha256=ONWR_KRz9au0O-XUUTrO_UN7GHTmZCTKyvflUQb8wxM,4996
340
340
  metrics/README.md,sha256=YwbCA2y6xJBlaO6yEtl1zWpqrQ4ZzkQSuQT-h6btET8,2307
341
341
  metrics/__init__.py,sha256=70sdDZGOwGIEFWgDkHWPMVODFelo206jp1g-BFV2u_4,90
342
- metrics/client.py,sha256=vc49J0KLLJlryhb77I-ejShDnWGF8NqSU6mhdPv8V2s,25570
342
+ metrics/client.py,sha256=-YPZfNJqbg86V3rmHTDx_raRLaZDjzKmFIl6BjeSHN4,25565
343
343
  metrics/eod.py,sha256=gnq-tNE7xfm2ah52e2TUyERgUQNwkFuT2rtDv8XOUVQ,9182
344
344
  metrics/examples/eod_example.py,sha256=gYtansjsKILVxe8XJD12XPaxmBJ-B6dOXGZG2JTGWA8,1664
345
345
  metrics/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -380,7 +380,7 @@ pushit/utils.py,sha256=IeTCGa-164nmB1jIsK1lu1O1QzUhS3BKfuXHGjCW-ck,2121
380
380
  rest/.gitignore,sha256=TbEvWRMnAiajCTOdhiNrd9eeCAaIjRp9PRjE_VkMM5g,118
381
381
  rest/README.md,sha256=V3ETc-cJu8PZIbKr9xSe_pA4JEUpC8Dhw4bQeVCDJPw,5460
382
382
  rest/RemoteEvents.py,sha256=nL46U7AuxIrlw2JunphR1tsXyqi-ep_gD9CYGpYbNgE,72
383
- rest/__init__.py,sha256=57yJpEKX4zpzd53NtME1OKpJzJqzJxz38KVjs86WJ0g,122
383
+ rest/__init__.py,sha256=BF5iQVc_58Sj3--bfe7Nv0kMyoAmtLcNU6EnH6vA1bU,122
384
384
  rest/arc4.py,sha256=y644IbF1ec--e4cUJ3KEYsewTCITK0gmlwa5mJruFC0,1967
385
385
  rest/cache.py,sha256=1Qg0rkaCJCaVP0-l5hZg2CIblTdeBSlj_0fP6vlKUpU,83
386
386
  rest/crypto/__init__.py,sha256=Tl0U11rgj1eBYqd6OXJ2_XSdNLumW_JkBZnaJqI6Ldw,72
@@ -395,12 +395,13 @@ rest/extra/__init__.py,sha256=YzmNsch5H5FFLkUK9mIAKyoRK_rJCA9HGb0kubp4h30,54
395
395
  rest/extra/hostinfo.py,sha256=5R23EafcNbFARyNEqdjBkqcpC8rPfmPd1zqNqle6-nM,4298
396
396
  rest/extra/json_metadata.py,sha256=p_ffzmANmOFix_oC3voR6_NNTjcn7-T7aXcH-I4_Npg,1078
397
397
  rest/fields.py,sha256=_v1TJVc6vyWlqmwFRJ6mtuR5Fo-lS0KcUhPWIrzKZUo,9719
398
+ rest/filetypes.py,sha256=wZXljB1g6JbA5H41xC81Jk3Wy_gu64ecNKU4k7aMcig,4171
398
399
  rest/forms.py,sha256=66Wm5cdy8tKib_mGicjq_yd-gNVMFWRECnrDksnNnwU,6316
399
400
  rest/helpers.py,sha256=t7smlOUzchVno-zeq7xMJIwogAR2DeSrffWxgysOHX8,29531
400
401
  rest/joke.py,sha256=0PpKaX2iN7jlS62kgjfmmqkFBYLPURz15aQ8R7OJkJ8,260
401
402
  rest/jwtoken.py,sha256=F7Vvpm31rAplTXr8XFP-Lb4BnDB3j1B2nQq0P1iTCLQ,2576
402
403
  rest/log.py,sha256=hd1_4HBOS395sfXJIL6BTw9yekm1SLgBwYx_PdfIhKA,20930
403
- rest/mail.py,sha256=FFvHgNcq84sJP5DyaJQKbuoao5NAsS6r-PT4MRfjWFU,8139
404
+ rest/mail.py,sha256=rp88V-SVM9F6ZIupoyrQvDwArhG0mfbzjHrDBRiHbvI,8204
404
405
  rest/mailman.py,sha256=v5O1G5s3HiAKmz-J1z0uT6_q3xsONPpxVl9saEyQQ2I,9174
405
406
  rest/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
407
  rest/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -413,7 +414,7 @@ rest/middleware/request.py,sha256=JchRNy5L-bGd-7h-KFYekGRvREe2eCkZXKOYqIkP2hI,41
413
414
  rest/middleware/session.py,sha256=zHSoQpIzRLmpqr_JvW406wzpvU3W3gDbm5JhtzLAMlE,10240
414
415
  rest/middleware/session_store.py,sha256=1nSdeXK8PyuYgGgIufqrS6j6QpIrQ7zbMNT0ol75e6U,1901
415
416
  rest/models/__init__.py,sha256=M8pvFDq-WCF-QcM58X7pMufYYe0aaQ3U0PwGe9TKbbY,130
416
- rest/models/base.py,sha256=oPj4m4-fnmjYUqd9dru17ulyh8jdFbXtWJt1CiuiBls,72633
417
+ rest/models/base.py,sha256=lbl20SK27Dsz3N6wcbBVEscrZ2n3VDZZH-gnqFgZz9s,72637
417
418
  rest/models/cacher.py,sha256=eKz8TINVhWEqKhJGMsRkKZTtBUIv5rN3NHbZwOC56Uk,578
418
419
  rest/models/metadata.py,sha256=ni8-BRF07lv4CdPUWnUdfPTOClQAVEeRZvO-ic623HU,12904
419
420
  rest/net.py,sha256=LcB2QV6VNRtsSdmiQvYZgwQUDwOPMn_VBdRiZ6OpI-I,2974
@@ -517,7 +518,7 @@ ws4redis/servers/uwsgi.py,sha256=VyhoCI1DnVFqBiJYHoxqn5Idlf6uJPHvfBKgkjs34mo,172
517
518
  ws4redis/settings.py,sha256=KKq00EwoGnz1yLwCZr5Dfoq2izivmAdsNEEM4EhZwN4,1610
518
519
  ws4redis/utf8validator.py,sha256=S0OlfjeGRP75aO6CzZsF4oTjRQAgR17OWE9rgZdMBZA,5122
519
520
  ws4redis/websocket.py,sha256=R0TUyPsoVRD7Y_oU7w2I6NL4fPwiz5Vl94-fUkZgLHA,14848
520
- django_restit-4.2.176.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
521
- django_restit-4.2.176.dist-info/METADATA,sha256=WVtqfF02L5jyZtQQoObLI57ZpcubGld7ZDyqc1vSXlg,7714
522
- django_restit-4.2.176.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
523
- django_restit-4.2.176.dist-info/RECORD,,
521
+ django_restit-4.2.178.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
522
+ django_restit-4.2.178.dist-info/METADATA,sha256=7mIBsuAJDUF-6Mbw7FgAVHoc7zqWDFiIKuLpbbieMZ8,7714
523
+ django_restit-4.2.178.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
524
+ django_restit-4.2.178.dist-info/RECORD,,
inbox/utils/render.py CHANGED
@@ -12,10 +12,17 @@ import re
12
12
 
13
13
 
14
14
  def createMessage(sender, recipients, subject, text, html, attachments=None, replyto=None):
15
- message = objict(sender=sender, recipients=recipients)
16
15
 
17
- if not isinstance(message.recipients, (tuple, list)):
18
- message.recipients = [message.recipients]
16
+ if isinstance(recipients, str):
17
+ if "," in recipients:
18
+ recipients = [t.strip() for t in recipients.split(',')]
19
+ elif ";" in recipients:
20
+ recipients = [t.strip() for t in recipients.split(';')]
21
+
22
+ if not isinstance(recipients, (tuple, list)):
23
+ recipients = [recipients]
24
+
25
+ message = objict(sender=sender, recipients=recipients)
19
26
 
20
27
  if attachments is None:
21
28
  attachments = []
medialib/models.py CHANGED
@@ -16,16 +16,14 @@ import urllib.request, urllib.error, urllib.parse
16
16
  from urllib.parse import urlparse
17
17
  from datetime import datetime, date
18
18
  import os
19
- import hashlib
20
19
  import time
21
- import mimetypes
22
20
  import tempfile
23
21
  import re
24
22
 
25
23
  from medialib import utils
26
24
  from rest import settings
27
25
  from rest.models import RestModel, MetaDataBase, MetaDataModel
28
- from rest.decorators import rest_async
26
+ from rest import filetypes
29
27
 
30
28
  from taskqueue.models import Task
31
29
 
@@ -547,7 +545,7 @@ class MediaItem(models.Model, RestModel, MetaDataModel):
547
545
  if kind in ["png", "jpg", "jpeg", "jfif", "bmp", "gif", "tif"]:
548
546
  self.kind = "I"
549
547
  return
550
- (mt, _) = mimetypes.guess_type(filename)
548
+ mt = filetypes.guess_type(filename)
551
549
  if mt == None:
552
550
  self.kind = '*'
553
551
  elif mt[:6] == 'image/':
@@ -833,7 +831,7 @@ class MediaItem(models.Model, RestModel, MetaDataModel):
833
831
  return image
834
832
  return {
835
833
  "kind": image.kind,
836
- "content_type": mimetypes.guess_type(image.url)[0],
834
+ "content_type": filetypes.guess_type(image.url),
837
835
  "bytes":image.bytes,
838
836
  "url":image.view_url(request=request, expires=None, is_secure=True),
839
837
  "width":image.width,
@@ -860,7 +858,7 @@ class MediaItem(models.Model, RestModel, MetaDataModel):
860
858
  return None
861
859
  return {
862
860
  "kind": orig.kind,
863
- "content_type": mimetypes.guess_type(self.name)[0],
861
+ "content_type": filetypes.guess_type(self.name),
864
862
  "bytes":orig.bytes,
865
863
  "url":orig.view_url(request=request, expires=None, is_secure=True),
866
864
  "width":orig.width,
@@ -1466,4 +1464,3 @@ class MediaItemParameterSetting(models.Model):
1466
1464
 
1467
1465
  def __str__(self):
1468
1466
  return "%s - %s" % (str(self.item), str(self.parameter))
1469
-
metrics/client.py CHANGED
@@ -329,7 +329,7 @@ class R(object):
329
329
  for s in slug:
330
330
  self.metric(s, num, category, expire, date)
331
331
  return
332
-
332
+
333
333
  # Add the slug to the set of metric slugs
334
334
  try:
335
335
  self.r.sadd(self._metric_slugs_key, slug)
@@ -382,12 +382,12 @@ class R(object):
382
382
  keys = utils.build_keys(slug, min_granularity=min_granularity, max_granularity=max_granularity)
383
383
 
384
384
  for granularity, key in zip(granularities, keys):
385
- rh.debug("-- get_metric --", granularity, key)
385
+ # rh.debug("-- get_metric --", granularity, key)
386
386
  try:
387
387
  results[granularity] = int(self.r.get(key))
388
388
  except Exception:
389
389
  results[granularity] = 0
390
- rh.log_exception("get_metric", granularity, key)
390
+ # rh.log_exception("get_metric", granularity, key)
391
391
  if min_granularity and min_granularity == max_granularity:
392
392
  return results[min_granularity]
393
393
  return results
@@ -661,4 +661,3 @@ class R(object):
661
661
  key = self._gauge_key(slug)
662
662
  self.r.delete(key) # Remove the Gauge
663
663
  self.r.srem(self._gauge_slugs_key, slug) # Remove from the set of keys
664
-
rest/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  from .uberdict import UberDict # noqa: F401
2
2
  from .settings_helper import settings # noqa: F401
3
3
 
4
- __version__ = "4.2.176"
4
+ __version__ = "4.2.178"
rest/filetypes.py ADDED
@@ -0,0 +1,128 @@
1
+ EXTENSION_TO_MIME = {
2
+ # Documents
3
+ '.pdf': 'application/pdf',
4
+ '.doc': 'application/msword',
5
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
6
+ '.xls': 'application/vnd.ms-excel',
7
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
8
+ '.ppt': 'application/vnd.ms-powerpoint',
9
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
10
+ '.csv': 'text/csv',
11
+ '.tsv': 'text/tab-separated-values',
12
+ '.txt': 'text/plain',
13
+ '.rtf': 'application/rtf',
14
+ '.odt': 'application/vnd.oasis.opendocument.text',
15
+ '.ods': 'application/vnd.oasis.opendocument.spreadsheet',
16
+ '.md': 'text/markdown',
17
+ '.epub': 'application/epub+zip',
18
+ '.ics': 'text/calendar',
19
+
20
+ # Images
21
+ '.jpg': 'image/jpeg',
22
+ '.jpeg': 'image/jpeg',
23
+ '.png': 'image/png',
24
+ '.gif': 'image/gif',
25
+ '.bmp': 'image/bmp',
26
+ '.webp': 'image/webp',
27
+ '.tiff': 'image/tiff',
28
+ '.svg': 'image/svg+xml',
29
+ '.heic': 'image/heic',
30
+ '.ico': 'image/vnd.microsoft.icon',
31
+
32
+ # Audio
33
+ '.mp3': 'audio/mpeg',
34
+ '.wav': 'audio/wav',
35
+ '.ogg': 'audio/ogg',
36
+ '.m4a': 'audio/mp4',
37
+ '.flac': 'audio/flac',
38
+
39
+ # Video
40
+ '.mp4': 'video/mp4',
41
+ '.mov': 'video/quicktime',
42
+ '.avi': 'video/x-msvideo',
43
+ '.wmv': 'video/x-ms-wmv',
44
+ '.webm': 'video/webm',
45
+ '.mkv': 'video/x-matroska',
46
+
47
+ # Archives
48
+ '.zip': 'application/zip',
49
+ '.tar': 'application/x-tar',
50
+ '.gz': 'application/gzip',
51
+ '.rar': 'application/vnd.rar',
52
+ '.7z': 'application/x-7z-compressed',
53
+ '.tar.gz': 'application/gzip', # Special case
54
+ '.tgz': 'application/gzip',
55
+ '.bz2': 'application/x-bzip2',
56
+ '.tar.bz2': 'application/x-bzip2',
57
+
58
+ # Code / Data
59
+ '.json': 'application/json',
60
+ '.xml': 'application/xml',
61
+ '.html': 'text/html',
62
+ '.htm': 'text/html',
63
+ '.css': 'text/css',
64
+ '.js': 'application/javascript',
65
+ '.py': 'text/x-python',
66
+ '.java': 'text/x-java-source',
67
+ '.c': 'text/x-c',
68
+ '.cpp': 'text/x-c++',
69
+ '.ts': 'application/typescript',
70
+ '.yml': 'application/x-yaml',
71
+ '.yaml': 'application/x-yaml',
72
+ '.sql': 'application/sql',
73
+
74
+ # Fonts
75
+ '.woff': 'font/woff',
76
+ '.woff2': 'font/woff2',
77
+ '.ttf': 'font/ttf',
78
+ '.otf': 'font/otf',
79
+
80
+ # Misc
81
+ '.apk': 'application/vnd.android.package-archive',
82
+ '.exe': 'application/vnd.microsoft.portable-executable',
83
+ '.dmg': 'application/x-apple-diskimage',
84
+ '.bat': 'application/x-bat',
85
+ '.sh': 'application/x-sh',
86
+ '.pdfa': 'application/pdf',
87
+ '.webmanifest': 'application/manifest+json',
88
+ }
89
+
90
+ def parse_extension(filename):
91
+ """
92
+ Extract the extension from a filename, handling both single and double extensions,
93
+ and return the extension along with the filename without the extension.
94
+
95
+ Parameters:
96
+ filename (str): The name of the file for which to extract the extension.
97
+
98
+ Returns:
99
+ tuple: A tuple containing the extracted extension (or an empty string if no valid
100
+ extension is found) and the filename without the extension.
101
+ """
102
+ filename = filename.lower()
103
+ parts = filename.rsplit('.', 2) # Max 2 splits: base.name.ext1.ext2
104
+ if len(parts) == 3:
105
+ double_ext = f".{parts[-2]}.{parts[-1]}"
106
+ if double_ext in EXTENSION_TO_MIME:
107
+ return double_ext, '.'.join(parts[:-1])
108
+ ext = f".{parts[-1]}" if '.' in filename else ''
109
+ base_filename = parts[0] if len(parts) == 1 else '.'.join(parts[:-1])
110
+ return ext.lower(), base_filename
111
+
112
+ def guess_type(filename):
113
+ """
114
+ Guess the MIME type of a file based on its extension.
115
+
116
+ This function attempts to determine the MIME type of a file by examining
117
+ its extension. It can handle both single and double extensions, such as
118
+ ".tar.gz" or ".tar.bz2".
119
+
120
+ Parameters:
121
+ filename (str): The name of the file whose MIME type needs to be guessed.
122
+
123
+ Returns:
124
+ str: The guessed MIME type if found in EXTENSION_TO_MIME mapping, or
125
+ 'application/octet-stream' as a default fallback.
126
+ """
127
+ ext, _ = parse_extension(filename)
128
+ return EXTENSION_TO_MIME.get(ext, 'application/octet-stream')
rest/mail.py CHANGED
@@ -3,7 +3,6 @@ import os
3
3
  import threading
4
4
  from io import StringIO
5
5
  import csv
6
- import mimetypes
7
6
  import boto3
8
7
 
9
8
  from email.mime.multipart import MIMEMultipart
@@ -25,6 +24,7 @@ from django.template import TemplateDoesNotExist
25
24
 
26
25
  import metrics
27
26
  from rest import settings
27
+ from rest import filetypes
28
28
  from rest.uberdict import UberDict
29
29
  from rest.middleware import get_request
30
30
  from rest.log import getLogger
@@ -163,7 +163,8 @@ def sendMail(msg, sender, recipients, fail_silently=True):
163
163
 
164
164
  def makeAttachment(filename, data):
165
165
  atment = UberDict(name=filename, data=data)
166
- atment.mimetype, junk = mimetypes.MimeTypes().guess_type(filename)
166
+ # atment.mimetype, junk = mimetypes.MimeTypes().guess_type(filename)
167
+ atment.mimetype = filetypes.guess_type(filename)
167
168
  return atment
168
169
 
169
170
 
rest/models/base.py CHANGED
@@ -1226,8 +1226,8 @@ class RestModel(object):
1226
1226
  @classmethod
1227
1227
  def on_rest_batch(cls, request, action):
1228
1228
  # this method is called when rest_batch='somme action'
1229
- if not ALLOW_BATCHING or not getattr(cls.RestMeta, "CAN_BATCH", True):
1230
- return GRAPH_HELPERS.restStatus(request, False, error="model does not allow batch actions")
1229
+ if not ALLOW_BATCHING or not getattr(cls.RestMeta, "CAN_BATCH", False):
1230
+ raise re.PermissionDeniedException(f"{cls.__name__} model does not allow batch actions", 439)
1231
1231
  cls._boundRest()
1232
1232
  # if not request.member.hasPerm("can_batch_update"):
1233
1233
  # raise re.PermissionDeniedException(f"batch updated not allowed by user")
@@ -1246,7 +1246,7 @@ class RestModel(object):
1246
1246
  if not can_delete:
1247
1247
  raise re.PermissionDeniedException(f"deletion not allowed for {cls.get_class_name()}", 438)
1248
1248
  count = qset.delete()[0]
1249
- return GRAPH_HELPERS.restStatus(request, True, error="delete {} items".format(count))
1249
+ return GRAPH_HELPERS.restStatus(request, True, error="deleted {} items".format(count))
1250
1250
  elif action == "update":
1251
1251
  update_fields = request.DATA.get(["batch_data", "batch_update"])
1252
1252
  if not isinstance(update_fields, dict):