2017-11-16 00:55:49 +01:00
|
|
|
import datetime
|
2019-02-02 23:53:19 +01:00
|
|
|
from typing import Optional
|
2017-11-16 00:55:49 +01:00
|
|
|
|
2016-07-29 21:52:45 +02:00
|
|
|
from django.db import models
|
2020-02-29 22:48:15 +01:00
|
|
|
from django.db.models import Q, UniqueConstraint
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-02-28 18:39:36 +01:00
|
|
|
from zerver.lib.timestamp import floor_to_day
|
2019-02-02 23:53:19 +01:00
|
|
|
from zerver.models import Realm, Stream, UserProfile
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-10-27 08:42:27 +02:00
|
|
|
class FillState(models.Model):
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
property: str = models.CharField(max_length=40, unique=True)
|
|
|
|
end_time: datetime.datetime = models.DateTimeField()
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# Valid states are {DONE, STARTED}
|
|
|
|
DONE = 1
|
|
|
|
STARTED = 2
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
state: int = models.PositiveSmallIntegerField()
|
2016-10-12 23:40:48 +02:00
|
|
|
|
2018-05-10 18:35:50 +02:00
|
|
|
def __str__(self) -> str:
|
2020-06-10 06:41:04 +02:00
|
|
|
return f"<FillState: {self.property} {self.end_time} {self.state}>"
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# The earliest/starting end_time in FillState
|
|
|
|
# We assume there is at least one realm
|
2017-11-22 07:55:37 +01:00
|
|
|
def installation_epoch() -> datetime.datetime:
|
analytics: Simplify frequency and measurement interval options.
Change the CountStat object to take an is_gauge variable instead of a
smallest_interval variable. Previously, (smallest_interval, frequency)
could be any of (hour, hour), (hour, day), (hour, gauge), (day, hour),
(day, day), or (day, gauge).
The current change is equivalent to excluding (hour, day) and (day, hour)
from the list above.
This change, along with other recent changes, allows us to simplify how we
handle time intervals. This commit also removes the TimeInterval object.
2016-10-14 00:15:46 +02:00
|
|
|
earliest_realm_creation = Realm.objects.aggregate(models.Min('date_created'))['date_created__min']
|
2017-02-28 18:39:36 +01:00
|
|
|
return floor_to_day(earliest_realm_creation)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-11-22 07:55:37 +01:00
|
|
|
def last_successful_fill(property: str) -> Optional[datetime.datetime]:
|
2017-02-08 08:04:10 +01:00
|
|
|
fillstate = FillState.objects.filter(property=property).first()
|
|
|
|
if fillstate is None:
|
|
|
|
return None
|
|
|
|
if fillstate.state == FillState.DONE:
|
|
|
|
return fillstate.end_time
|
|
|
|
return fillstate.end_time - datetime.timedelta(hours=1)
|
|
|
|
|
2017-10-27 08:42:27 +02:00
|
|
|
class BaseCount(models.Model):
|
2016-10-06 23:25:55 +02:00
|
|
|
# Note: When inheriting from BaseCount, you may want to rearrange
|
|
|
|
# the order of the columns in the migration to make sure they
|
|
|
|
# match how you'd like the table to be arranged.
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
property: str = models.CharField(max_length=32)
|
|
|
|
subgroup: Optional[str] = models.CharField(max_length=16, null=True)
|
|
|
|
end_time: datetime.datetime = models.DateTimeField()
|
|
|
|
value: int = models.BigIntegerField()
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-11-05 11:30:44 +01:00
|
|
|
class Meta:
|
2016-07-29 21:52:45 +02:00
|
|
|
abstract = True
|
|
|
|
|
|
|
|
class InstallationCount(BaseCount):
|
|
|
|
|
2017-11-05 11:30:44 +01:00
|
|
|
class Meta:
|
2020-02-29 22:48:15 +01:00
|
|
|
# Handles invalid duplicate InstallationCount data
|
|
|
|
constraints = [
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["property", "subgroup", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=False),
|
|
|
|
name='unique_installation_count'),
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["property", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=True),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
name='unique_installation_count_null_subgroup'),
|
2020-02-29 22:48:15 +01:00
|
|
|
]
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2018-05-10 18:35:50 +02:00
|
|
|
def __str__(self) -> str:
|
2020-06-10 06:41:04 +02:00
|
|
|
return f"<InstallationCount: {self.property} {self.subgroup} {self.value}>"
|
2016-07-29 21:52:45 +02:00
|
|
|
|
|
|
|
class RealmCount(BaseCount):
|
2018-01-29 08:17:31 +01:00
|
|
|
realm = models.ForeignKey(Realm, on_delete=models.CASCADE)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-11-05 11:30:44 +01:00
|
|
|
class Meta:
|
2020-02-29 22:48:15 +01:00
|
|
|
# Handles invalid duplicate RealmCount data
|
|
|
|
constraints = [
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["realm", "property", "subgroup", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=False),
|
|
|
|
name='unique_realm_count'),
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["realm", "property", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=True),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
name='unique_realm_count_null_subgroup'),
|
2020-02-29 22:48:15 +01:00
|
|
|
]
|
2017-02-01 23:29:55 +01:00
|
|
|
index_together = ["property", "end_time"]
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2018-05-10 18:35:50 +02:00
|
|
|
def __str__(self) -> str:
|
2020-06-10 06:41:04 +02:00
|
|
|
return f"<RealmCount: {self.realm} {self.property} {self.subgroup} {self.value}>"
|
2016-07-29 21:52:45 +02:00
|
|
|
|
|
|
|
class UserCount(BaseCount):
|
2018-01-29 08:17:31 +01:00
|
|
|
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
|
|
|
realm = models.ForeignKey(Realm, on_delete=models.CASCADE)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-11-05 11:30:44 +01:00
|
|
|
class Meta:
|
2020-02-29 22:48:15 +01:00
|
|
|
# Handles invalid duplicate UserCount data
|
|
|
|
constraints = [
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["user", "property", "subgroup", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=False),
|
|
|
|
name='unique_user_count'),
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["user", "property", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=True),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
name='unique_user_count_null_subgroup'),
|
2020-02-29 22:48:15 +01:00
|
|
|
]
|
2017-02-01 23:29:55 +01:00
|
|
|
# This index dramatically improves the performance of
|
|
|
|
# aggregating from users to realms
|
|
|
|
index_together = ["property", "realm", "end_time"]
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2018-05-10 18:35:50 +02:00
|
|
|
def __str__(self) -> str:
|
2020-06-10 06:41:04 +02:00
|
|
|
return f"<UserCount: {self.user} {self.property} {self.subgroup} {self.value}>"
|
2016-07-29 21:52:45 +02:00
|
|
|
|
|
|
|
class StreamCount(BaseCount):
|
2018-01-29 08:17:31 +01:00
|
|
|
stream = models.ForeignKey(Stream, on_delete=models.CASCADE)
|
|
|
|
realm = models.ForeignKey(Realm, on_delete=models.CASCADE)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-11-05 11:30:44 +01:00
|
|
|
class Meta:
|
2020-02-29 22:48:15 +01:00
|
|
|
# Handles invalid duplicate StreamCount data
|
|
|
|
constraints = [
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["stream", "property", "subgroup", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=False),
|
|
|
|
name='unique_stream_count'),
|
|
|
|
UniqueConstraint(
|
|
|
|
fields=["stream", "property", "end_time"],
|
|
|
|
condition=Q(subgroup__isnull=True),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
name='unique_stream_count_null_subgroup'),
|
2020-02-29 22:48:15 +01:00
|
|
|
]
|
2017-02-01 23:29:55 +01:00
|
|
|
# This index dramatically improves the performance of
|
|
|
|
# aggregating from streams to realms
|
|
|
|
index_together = ["property", "realm", "end_time"]
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2018-05-10 18:35:50 +02:00
|
|
|
def __str__(self) -> str:
|
2020-06-10 06:41:04 +02:00
|
|
|
return f"<StreamCount: {self.stream} {self.property} {self.subgroup} {self.value} {self.id}>"
|