class Transmitter(BaseModel): repeater = models.ForeignKey(Repeater, on_delete=models.CASCADE, related_name='transmitters') active = models.BooleanField(default=True) transmit_frequency = models.DecimalField(max_digits=18, decimal_places=6) offset = models.DecimalField(max_digits=18, decimal_places=6) mode = models.CharField(max_length=16, choices=RF_MODES) pep = models.FloatField("PEP", null=True, blank=True, help_text="Peak Envelope Power in W") description = models.TextField(blank=True, null=True) hardware = models.CharField(max_length=256, blank=True) # Analog ctcss = models.DecimalField( "CTCSS", choices=CTCSS, decimal_places=1, max_digits=5, blank=True, null=True, help_text="Continuous Tone Coded Squelch System") echolink = models.IntegerField(blank=True, null=True) # Digital dmr_id = models.ForeignKey(DMRID, on_delete=models.PROTECT, related_name="transmitters", verbose_name="DMR ID", blank=True, null=True) colorcode = models.SmallIntegerField(blank=True, null=True) created_by = models.ForeignKey(get_user_model(), on_delete=models.SET(get_sentinel_user), related_name="transmitters") source = models.CharField(max_length=256, blank=True) @property def receive_frequency(self) -> Decimal: if self.transmit_frequency and self.offset is not None: return self.transmit_frequency + self.offset else: return Decimal(0) @property def brandmeister_repeater_url(self) -> str: if self.dmr_id: return f"https://brandmeister.network/index.php?page=repeater&id={ self.dmr_id.name }" else: return "" def __str__(self) -> str: return f"{ self.repeater.callsign.name } at { self.transmit_frequency } MHz"
class Club(BaseModel): callsign = models.OneToOneField(Callsign, on_delete=models.CASCADE) website = models.URLField(blank=True) description = models.TextField(blank=True) members = models.ManyToManyField(Callsign, related_name="members", blank=True) created_by = models.ForeignKey(get_user_model(), on_delete=models.SET(get_sentinel_user), related_name="clubs") def get_absolute_url(self) -> str: return reverse('callsign:callsign-html-detail', args=[self.callsign.name]) def __str__(self) -> str: return self.callsign.name
class Repeater(LocationBaseModel): callsign = models.OneToOneField(Callsign, on_delete=models.CASCADE) active = models.BooleanField(default=True) website = models.URLField(max_length=400, blank=True, null=True) altitude = models.FloatField(blank=True, null=True) description = models.TextField(blank=True) created_by = models.ForeignKey(get_user_model(), on_delete=models.SET(get_sentinel_user), related_name="repeaters") source = models.CharField(max_length=256, blank=True) def __str__(self) -> str: return self.callsign.name def get_absolute_url(self) -> str: return reverse('callsign:callsign-html-detail', args=[self.callsign.name])
class Shareditem(models.Model): """ Parent model for all shared items on the page. The class is using multiple managers for * inheritance and selecting subclasses (default 'objects'), and * for spatial queries ('geo_objects'). """ ITEMTYPES = ( ("i", "Idea"), ("m", "Meeting Note"), ("n", "Newspaper Article"), ("e", "Photo or Video"), # legacy: was 'External Media' ("d", "Data"), ) # used in template tags, added here for maintenance reasons ITEMTYPES_PLURAL = { "i": "Ideas", "m": "Meeting Notes", "n": "Newspaper Articles", "e": "Photos & Videos", "d": "Data", } desc = MarkupField( "Description", help_text= "Please see the <a href='#'>Text formatting cheat sheet</a> for help.") itemtype = models.CharField( max_length=1, choices=ITEMTYPES, ) station = models.ForeignKey("Station", verbose_name="Related Station", null=True, blank=True) theme = models.ForeignKey("Theme", verbose_name="Related Theme", null=True, blank=True) author = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user)) ip = models.IPAddressField(default="127.0.0.1") created = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now_add=True, auto_now=True) rating = RatingField(range=5, can_change_vote=True) objects = InheritanceManager() geometry = models.PointField(geography=True, null=True, blank=True) # default SRS 4326 geo_objects = models.GeoManager() class Meta: ordering = ("-created", "author") get_latest_by = "created" def __unicode__(self): return u"%i" % self.id # child model per itemtype CHILDMODELS = { "i": "idea", "m": "meetingnote", "n": "newsarticle", "e": "media", "d": "data", } @permalink def get_absolute_url(self): return ("%s_detail" % (Shareditem.CHILDMODELS[self.itemtype]), None, { "id": self.id, }) def get_comment_count(self): # workaround for problem with for_model method and inheritance contenttype = ContentType.objects.get_for_model(self) return Comment.objects.filter(content_type=contenttype.id, object_pk=self.id).count() def get_child_contenttype(self): return ContentType.objects.get( app_label="participation", model=Shareditem.CHILDMODELS[self.itemtype])
class Walk(models.Model): """ A Walk contains the route of the walk and all location data about the start location """ name = models.CharField(max_length=255) slug = models.SlugField(max_length=255) description = models.TextField() start = models.PointField(blank=True, null=True) route = models.LineStringField() submitter = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user)) route_length = models.FloatField( help_text="The length of the walk in miles", default=0 ) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) # Reverse Geocoding from OpenCage reverse_geocode_cache_time = models.DateTimeField(blank=True, null=True) # annotations what3words = models.CharField(max_length=100, blank=True, null=True) geohash = models.CharField(max_length=50, blank=True, null=True) # components continent = models.CharField(max_length=100, blank=True, null=True) country = models.CharField(max_length=100, blank=True, null=True) state = models.CharField(max_length=100, blank=True, null=True) county = models.CharField(max_length=100, blank=True, null=True) city = models.CharField(max_length=100, blank=True, null=True) suburb = models.CharField(max_length=100, blank=True, null=True) road = models.CharField(max_length=100, blank=True, null=True) postcode = models.CharField(max_length=100, blank=True, null=True) formatted = models.CharField(max_length=500, blank=True, null=True) attributes = models.ManyToManyField(Attribute, blank=True) def get_absolute_url(self): return reverse( "walk-detail", kwargs={"username": self.submitter.username, "slug": self.slug}, ) def __str__(self): return self.name def save(self, *args, **kwargs): initial_start = self.start self.slug = slugify(self.name) super().save(*args, **kwargs) # Check if the start location has changed start_location_changed = False if not self.start: self.start = self.calculate_walk_start() start_location_changed = True if self.start != initial_start: start_location_changed = True # check if the cache_has expired if self.reverse_geocode_cache_time is None: cache_expired = True else: cache_expired = self.reverse_geocode_cache_time < ( now() - timedelta(days=180) ) # update the location details if we need to if start_location_changed or cache_expired: if settings.OPENCAGE_API_KEY: geocoder = OpenCageGeocode(settings.OPENCAGE_API_KEY) try: results = geocoder.reverse_geocode( round(self.start[1], 6), round(self.start[0], 6), language="en", limit=1, ) if results and len(results): self.what3words = ( results[0].get("annotations").get("what3words").get("words") ) self.geohash = results[0].get("annotations").get("geohash") self.continent = results[0].get("components").get("continent") self.country = results[0].get("components").get("country") self.state = results[0].get("components").get("state") self.county = results[0].get("components").get("county") self.city = results[0].get("components").get("city") self.suburb = results[0].get("components").get("suburb") self.road = results[0].get("components").get("road") self.postcode = results[0].get("components").get("postcode") self.formatted = results[0].get("formatted") self.reverse_geocode_cache_time = now() super().save(*args, **kwargs) except RateLimitExceededError as e: print(e) self.route.transform(3857) self.route_length = D(m=self.route.length).mi super().save(*args, **kwargs) def calculate_walk_start(self): return Point(srid=self.route.srid, x=self.route[0][0], y=self.route[0][1],)
class Callsign(LocationBaseModel): name = CallsignField(unique=True, db_index=True) prefix = models.ForeignKey(CallsignPrefix, on_delete=models.PROTECT, null=True, blank=True) country = models.ForeignKey(Country, on_delete=models.PROTECT, null=True, blank=True) cq_zone = CQZoneField("CQ zone", null=True, blank=True) itu_zone = ITUZoneField("ITU zone", null=True, blank=True) itu_region = ITURegionField("ITU region", null=True, blank=True) type = models.CharField(choices=CALLSIGN_TYPES, max_length=32, blank=True) owner = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True, blank=True) active = models.BooleanField(default=True) issued = models.DateField(null=True, blank=True) expired = models.DateField(null=True, blank=True) license_type = models.CharField(max_length=64, blank=True) dstar = models.BooleanField("D-STAR", default=False) identifier = models.CharField(_("Optional identifier"), max_length=128, unique=True, blank=True, null=True) website = models.URLField(max_length=128, blank=True, null=True) comment = models.TextField(blank=True) _official_validated = models.BooleanField( default=False, help_text="Callsign is validated by a government agency") _location_source = models.CharField(max_length=32, choices=LOCATION_SOURCE_CHOICES, blank=True) lotw_last_activity = models.DateTimeField("LOTW last activity", null=True, blank=True) eqsl = models.BooleanField(default=False) created_by = models.ForeignKey(get_user_model(), on_delete=models.SET(get_sentinel_user), related_name="callsigns") internal_comment = models.TextField(blank=True) source = models.CharField(max_length=256, blank=True) objects = CallsignManager() # TODO(elnappo) make sure a user can not change his name after validation def __str__(self) -> str: return self.name def set_default_meta_data(self): prefix = CallsignPrefix.objects.extra( where=["%s LIKE name||'%%'"], params=[self.name]).order_by("-name").first() # Add changed fields to ImportCommand._callsign_bulk_create() if prefix: self.prefix = prefix self.country = prefix.country self.cq_zone = prefix.cq_zone self.itu_zone = prefix.itu_zone self.location = prefix.location self._location_source = "prefix" def get_absolute_url(self) -> str: return reverse('callsign:callsign-html-detail', args=[self.name]) def update_location(self, location: Point, source: str) -> bool: # Update location only if new location source has higher priority than current location source. # Does no update if new and current location source are equal. if LOCATION_SOURCE_PRIORITY.index( source) > LOCATION_SOURCE_PRIORITY.index(self.location_source): self.location = location self._location_source = source return True else: return False @property def aprs_passcode(self) -> int: if self.official_validated == "false" or self.type == "shortwave_listener": # Not a good idea to raise an exception here? raise PermissionDenied( "callsign is not official assigned or is shortwave listener") return generate_aprs_passcode(self.name) @property def official_validated(self) -> str: if self.country and self.country.telecommunicationagency.used_for_official_callsign_import and self._official_validated: return "true" elif self.country and self.country.telecommunicationagency.used_for_official_callsign_import and not self._official_validated: return "false" else: return "unknown" @property def location_source(self) -> str: if self._location_source in ("official", "unofficial"): return "address" else: return self._location_source @property def grid(self): if self.location_source == "prefix": return self._grid(high_accuracy=False) else: return self._grid(high_accuracy=True) @property def lotw(self) -> bool: return bool(self.lotw_last_activity) @property def eqsl_profile_url(self) -> str: return f"https://www.eqsl.cc/Member.cfm?{ self.name }" @property def clublog_profile_url(self) -> str: return f"https://secure.clublog.org/logsearch/{ self.name }" @property def dxheat_profile_url(self) -> str: return f"https://dxheat.com/db/{ self.name }" @property def aprsfi_profile_url(self) -> str: return f"https://aprs.fi/info/?call={ self.name }" @property def pskreporter_profile_url(self) -> str: return f"http://www.pskreporter.de/table?call={ self.name }" @property def qrzcq_profile_url(self) -> str: return f"https://www.qrzcq.com/call/{ self.name }" @property def qrz_profile_url(self) -> str: return f"https://www.qrz.com/db/{ self.name }" @property def hamqth_profile_url(self) -> str: return f"https://www.hamqth.com/{ self.name }" @property def hamcall_profile_url(self) -> str: return f"https://hamcall.net/call?callsign={ self.name }" @property def dxwatch_profile_url(self) -> str: return f"https://dxwatch.com/qrz/{self.name}"