Coverage for jsanctions/models.py: 93%

# Coverage for jsanctions/models.py : 93%

## 221 statements

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1 import logging

2 import os

3 from urllib.request import urlopen

4 from django.core.files import File

5 from django.core.files.base import ContentFile

6 from django.core.serializers.json import DjangoJSONEncoder

7 from django.core.validators import FileExtensionValidator

8 from django.db import models

9 from django.utils.timezone import now

10 from django.utils.translation import gettext_lazy as _

11 from jutil.format import is_media_full_path, strip_media_root, get_media_full_path

12 from jutil.modelfields import SafeCharField, SafeTextField

13

14 from jsanctions.helpers import get_country_iso2_code

15

16 logger = logging.getLogger(__name__)

17

18

19 REMARK_BRIEF_LENGTH = 128

20 DEFAULT_DESCRIPTION_TYPE = {"blank": True, "max_length": 512, "default": ""}

21 DEFAULT_REMARK_TYPE = {

22 "verbose_name": _("remark"),

23 "null": True,

24 "default": None,

25 "blank": True,

26 "on_delete": models.SET_NULL,

27}

28 DEFAULT_CODE_TYPE = {"blank": True, "max_length": 32, "default": ""}

29 DEFAULT_DATE_TYPE = {"blank": True, "null": True, "default": None}

30 LANGUAGE_CODE_TYPE = {"blank": True, "default": "", "max_length": 5}

31 COUNTRY_CODE_TYPE = {"blank": True, "default": "", "max_length": 3}

32 DEFAULT_BOOLEAN_TYPE = {"blank": True, "default": None, "null": True}

33 DEFAULT_INT_TYPE = {"blank": True, "default": None, "null": True}

34 REGULATION_SUMMARY_TYPE = {

35 "verbose_name": _("regulation summary"),

36 "blank": True,

37 "default": None,

38 "null": True,

39 "on_delete": models.PROTECT,

40}

41

42

43 class SanctionsListFileManager(models.Manager):

44 def create_from_filename(self, filename: str, **kwargs):

45 full_path = os.path.realpath(filename)

46 file = self.create(**kwargs)

47 assert isinstance(file, SanctionsListFile)

48 if is_media_full_path(full_path):

49 file.file.name = strip_media_root(full_path)

50 file.save()

51 logger.info('%s used as is', file.file)

52 else:

53 with open(full_path, "rb") as fp:

54 plain_filename = os.path.basename(filename)

55 file.file.save(plain_filename, File(fp))

56 logger.info('%s written', file.file)

57 return file

58

59 def create_from_url(self, url: str, filename: str, **kwargs):

60 response = urlopen(url)

61 body = response.read()

62 plain_filename = os.path.basename(filename)

63 file = self.create(**kwargs)

64 file.file.save(plain_filename, ContentFile(body))

65 logger.info('%s written', file.file)

66 return file

67

68

69 class SanctionListObject(models.Model):

70 pass

71

72

73 class SanctionsListFile(SanctionListObject):

74 objects = SanctionsListFileManager() # type: ignore

75 created = models.DateTimeField(verbose_name=_("created"), default=now, blank=True, editable=False, db_index=True)

76 imported = models.DateTimeField(

77 verbose_name=_("imported"), default=None, null=True, blank=True, editable=False, db_index=True

78 )

79 generation_date = models.DateField(

80 verbose_name=_("generation date"), default=None, blank=True, null=True, editable=False, db_index=True

81 )

82 file = models.FileField(verbose_name=_("file"), upload_to="uploads", validators=[FileExtensionValidator(["xml"])])

83 list_type = SafeCharField(verbose_name=_("list type"), max_length=128, db_index=True)

84 global_file_id = SafeCharField(verbose_name=_("global file id"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

85

86 class Meta:

87 verbose_name = _("sanction list")

88 verbose_name_plural = _("sanction lists")

89

90 def __str__(self):

91 return "{}".format(os.path.basename(self.file.name))

92

93 @property

94 def full_path(self) -> str:

95 return get_media_full_path(self.file.name)

96

97

98 class Remark(models.Model):

99 container = models.ForeignKey(SanctionListObject, on_delete=models.CASCADE)

100 text = SafeTextField(verbose_name=_("text"), blank=True)

101

102 class Meta:

103 verbose_name = _("remark")

104 verbose_name_plural = _("remarks")

105

106 def __str__(self) -> str:

107 return str(self.text)

108

109 @property

110 def text_brief(self) -> str:

111 return self.text if len(self.text) < REMARK_BRIEF_LENGTH else self.text[:REMARK_BRIEF_LENGTH] + "..."

112

113

114 class SubjectType(SanctionListObject):

115 PERSON = 'P'

116 ENTERPRISE = 'E'

117 VESSEL = 'V'

118 AIRCRAFT = 'A'

119 CLASSIFICATION_CODES = [

120 (PERSON, _('person')),

121 (ENTERPRISE, _('enterprise')),

122 (VESSEL, _('vessel')),

123 (AIRCRAFT, _('aircraft')),

124 ]

125

126 classification_code = SafeCharField(verbose_name=_("classification code"), **DEFAULT_CODE_TYPE) # type: ignore

127 code = SafeCharField(verbose_name=_("code"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

128

129 class Meta:

130 verbose_name = _("subject type")

131 verbose_name_plural = _("subject types")

132

133 def __str__(self) -> str:

134 return str(self.code)

135

136

137 class Regulation(SanctionListObject):

138 sanction = models.ForeignKey("SanctionEntity", verbose_name=_("sanction entity"), on_delete=models.CASCADE)

139 regulation_type = SafeCharField(verbose_name=_("regulation type"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

140 organisation_type = SafeCharField(verbose_name=_("organization type"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

141 publication_date = models.DateField(verbose_name=_("publication date"), **DEFAULT_DATE_TYPE) # type: ignore

142 publication_url = models.URLField(verbose_name=_("url"), blank=True, default="")

143 entry_into_force_date = models.DateField(verbose_name=_("entry into force date"), **DEFAULT_DATE_TYPE) # type: ignore

144 number_title = SafeCharField(verbose_name=_("number title"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

145 programme = SafeCharField(verbose_name=_("programmer"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

146 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

147

148 class Meta:

149 verbose_name = _("regulation")

150 verbose_name_plural = _("regulations")

151

152

153 class RegulationSummary(SanctionListObject):

154 regulation_type = SafeCharField(verbose_name=_("regulation type"), **DEFAULT_CODE_TYPE) # type: ignore

155 number_title = SafeCharField(verbose_name=_("number title"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

156 publication_date = models.DateField(verbose_name=_("publication date"), **DEFAULT_DATE_TYPE) # type: ignore

157 publication_url = models.URLField(verbose_name=_("url"), blank=True, default="")

158

159 class Meta:

160 verbose_name = _("regulation summary")

161 verbose_name_plural = _("regulation summaries")

162

163 def __str__(self) -> str:

164 return "{} {}".format(self.regulation_type, self.number_title)

165

166

167 class NameAlias(SanctionListObject):

168 sanction = models.ForeignKey("SanctionEntity", verbose_name=_("sanction entity"), on_delete=models.CASCADE)

169 first_name = SafeCharField(verbose_name=_("first name"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

170 middle_name = SafeCharField(verbose_name=_("middle name"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

171 last_name = SafeCharField(verbose_name=_("last name"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

172 whole_name = SafeCharField(verbose_name=_("whole name"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

173 name_language = SafeCharField(verbose_name=_("name language"), **LANGUAGE_CODE_TYPE) # type: ignore

174 function = SafeCharField(verbose_name=_("function"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

175 title = SafeCharField(verbose_name=_("title"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

176 regulation_language = SafeCharField(verbose_name=_("regulation language"), **LANGUAGE_CODE_TYPE) # type: ignore

177 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

178 regulation_summary = models.ForeignKey(RegulationSummary, **REGULATION_SUMMARY_TYPE) # type: ignore

179

180 class Meta:

181 verbose_name = _("name alias")

182 verbose_name_plural = _("name aliases")

183

184 def __str__(self) -> str:

185 return str(self.whole_name)

186

187 def clean(self):

188 if len(self.function) > 256:

189 self.function = self.function[:256]

190 if not self.whole_name:

191 self.whole_name = (self.first_name + ' ' + self.last_name).strip()

192

193

194 class Identification(SanctionListObject):

195 sanction = models.ForeignKey("SanctionEntity", verbose_name=_("sanction entity"), on_delete=models.CASCADE)

196 diplomatic = models.BooleanField(verbose_name=_("diplomatic"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

197 known_expired = models.BooleanField(verbose_name=_("known expired"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

198 known_false = models.BooleanField(verbose_name=_("known false"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

199 reported_lost = models.BooleanField(verbose_name=_("reported lost"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

200 revoked_by_issuer = models.BooleanField(verbose_name=_("revoked by issuer"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

201 issue_date = models.DateField(verbose_name=_("issue date"), **DEFAULT_DATE_TYPE) # type: ignore

202 issued_by = SafeCharField(verbose_name=_("issued by"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

203 latin_number = SafeCharField(verbose_name=_("latin number"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

204 name_on_document = SafeCharField(verbose_name=_("name on document"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

205 number = SafeCharField(verbose_name=_("number"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

206 region = SafeCharField(verbose_name=_("region"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

207 country_iso2_code = SafeCharField(verbose_name=_("issued by"), **COUNTRY_CODE_TYPE) # type: ignore

208 country_description = SafeCharField(verbose_name=_("country description"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

209 identification_type_code = SafeCharField(verbose_name=_("identification type code"), **DEFAULT_CODE_TYPE) # type: ignore

210 identification_type_description = SafeCharField(verbose_name=_("identification type code"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

211 regulation_language = SafeCharField(verbose_name=_("regional language"), **LANGUAGE_CODE_TYPE) # type: ignore

212 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

213 regulation_summary = models.ForeignKey(RegulationSummary, **REGULATION_SUMMARY_TYPE) # type: ignore

214

215 class Meta:

216 verbose_name = _("identification")

217 verbose_name_plural = _("identifications")

218

219 def clean(self):

220 if self.country_description and not self.country_iso2_code:

221 self.country_iso2_code = get_country_iso2_code(self.country_description)

222

223

224 class BirthDate(SanctionListObject):

225 sanction = models.ForeignKey("SanctionEntity", verbose_name=_("sanction entity"), on_delete=models.CASCADE)

226 circa = models.BooleanField(verbose_name=_("circa"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

227 calendar_type = SafeCharField(verbose_name=_("calendar type"), **DEFAULT_CODE_TYPE) # type: ignore

228 city = SafeCharField(verbose_name=_("city"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

229 zip_code = SafeCharField(verbose_name=_("zip code"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

230 birth_date = models.DateField(verbose_name=_("birth date"), **DEFAULT_DATE_TYPE) # type: ignore

231 birth_date_description = SafeCharField(verbose_name=_("zip code"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

232 day_of_month = models.IntegerField(verbose_name=_("day of month"), **DEFAULT_INT_TYPE) # type: ignore

233 month_of_year = models.IntegerField(verbose_name=_("month of year"), **DEFAULT_INT_TYPE) # type: ignore

234 year = models.IntegerField(verbose_name=_("year"), **DEFAULT_INT_TYPE) # type: ignore

235 region = SafeCharField(verbose_name=_("region"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

236 place = SafeCharField(verbose_name=_("place"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

237 country_iso2_code = SafeCharField(verbose_name=_("country"), **COUNTRY_CODE_TYPE) # type: ignore

238 country_description = SafeCharField(verbose_name=_("country description"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

239 regulation_language = SafeCharField(verbose_name=_("regional language"), **LANGUAGE_CODE_TYPE) # type: ignore

240 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

241

242 class Meta:

243 verbose_name = _("birth date")

244 verbose_name_plural = _("birth dates")

245

246

247 class Citizenship(SanctionListObject):

248 sanction = models.ForeignKey("SanctionEntity", verbose_name=_("sanction entity"), on_delete=models.CASCADE)

249 region = SafeCharField(verbose_name=_("region"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

250 country_iso2_code = SafeCharField(verbose_name=_("country"), **COUNTRY_CODE_TYPE) # type: ignore

251 country_description = SafeCharField(verbose_name=_("country description"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

252 regulation_language = SafeCharField(verbose_name=_("regional language"), **LANGUAGE_CODE_TYPE) # type: ignore

253 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

254 regulation_summary = models.ForeignKey(RegulationSummary, **REGULATION_SUMMARY_TYPE) # type: ignore

255

256 class Meta:

257 verbose_name = _("citizenship")

258 verbose_name_plural = _("citizenships")

259

260

261 class Address(SanctionListObject):

262 sanction = models.ForeignKey("SanctionEntity", verbose_name=_("sanction entity"), on_delete=models.CASCADE)

263 city = SafeCharField(verbose_name=_("city"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

264 street = SafeCharField(verbose_name=_("street"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

265 po_box = SafeCharField(verbose_name=_("p.o. box"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

266 zip_code = SafeCharField(verbose_name=_("zip code"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

267 as_at_listing_time = models.BooleanField(_("as at listing time"), **DEFAULT_BOOLEAN_TYPE) # type: ignore

268 place = SafeCharField(verbose_name=_("place"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

269 region = SafeCharField(verbose_name=_("region"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

270 country_iso2_code = SafeCharField(verbose_name=_("country"), **COUNTRY_CODE_TYPE) # type: ignore

271 country_description = SafeCharField(verbose_name=_("country description"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

272 regulation_language = SafeCharField(verbose_name=_("regional language"), **LANGUAGE_CODE_TYPE) # type: ignore

273 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

274 regulation_summary = models.ForeignKey(RegulationSummary, **REGULATION_SUMMARY_TYPE) # type: ignore

275

276 class Meta:

277 verbose_name = _("address")

278 verbose_name_plural = _("addresses")

279

280 def clean(self):

281 if self.country_description and not self.country_iso2_code:

282 self.country_iso2_code = get_country_iso2_code(self.country_description)

283

284

285 class SanctionEntity(SanctionListObject):

286 source = models.ForeignKey(SanctionsListFile, verbose_name=_("source"), on_delete=models.CASCADE)

287 designation_details = SafeCharField(verbose_name=_("designation details"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

288 united_nation_id = SafeCharField(verbose_name=_("United Nation identifier"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

289 eu_reference_number = SafeCharField(verbose_name=_("EU reference number"), **DEFAULT_DESCRIPTION_TYPE) # type: ignore

290 logical_id = models.BigIntegerField(verbose_name=_("logical id"), blank=True, null=True, default=None)

291 subject_type = models.ForeignKey(SubjectType, verbose_name=_("subject type"), on_delete=models.PROTECT, null=True, default=None, blank=True)

292 data = models.JSONField(_('data'), default=dict, blank=True, encoder=DjangoJSONEncoder) # type: ignore

293

294 class Meta:

295 verbose_name = _("sanction entity")

296 verbose_name_plural = _("sanction entities")