class Transaction(models.Model):
    """
    Model that stores the data for every transaction, to allow better monitoring of the status of the whole system.

    Each change to any piece of gear should be done via a transaction manager function. This allows transactions to
    serve as state change vectors for the system, and the status of the system at any time could be reconstructed from
    the addition (subsequent application) of these transactions.

    NEVER CREATE TRANSACTIONS MANUALLY, ALWAYS USE THE MANAGER FUNCTIONS
    """

    objects = TransactionManager()

    transaction_types = [
        ("Rental", (
            ("CheckOut",  "Check Out"),
            ("CheckIn",   "Check In"),
            ("Inventory", "In Stock")
        )
         ),
        ("Admin Actions", (
            ("Create",   "New Gear"),
            ("Delete",   "Remove Gear"),
            ("ReTag",    "Change Tag"),
            ("Break",    "Set Broken"),
            ("Fix",      "Set Fixed"),
            ("Override", "Admin Change")
        )
         ),
        ("Auto Updates", (
            ("Missing", "Gear Missing"),
            ("Expire",  "Gear Expiration"),
            )
         )
    ]

    primary_key = PrimaryKeyField()

    #: The time at which this transaction was created - will be automatically set and cannot be changed
    timestamp = models.DateTimeField(auto_now_add=True)

    #: A string defining the type of transaction this is (see transaction types)
    type = models.CharField(max_length=20, choices=transaction_types)

    #: The piece of gear this transaction relates to: MUST EXIST
    gear = models.ForeignKey(Gear, null=False, on_delete=models.PROTECT, related_name="has_checked_out",
                             validators=[validate_available])

    #: If this transaction relates to a member, that member should be referenced here
    member = models.ForeignKey(Member, null=True, on_delete=models.PROTECT)

    #: Either SYSTEM or a String of the rfid of the person who authorized the transaction
    authorizer = models.ForeignKey(Member, null=False, on_delete=models.PROTECT,
                                   related_name="has_authorized", validators=[validate_auth])

    #: Any additional notes to be saved about this transaction
    comments = models.TextField(default="")

    def __str__(self):
        return "{} Transaction for a {}".format(self.type, self.gear.name)

    @property
    def detail_url(self):
        return reverse("admin:core_transaction_detail", kwargs={"pk": self.pk})
Beispiel #2
0
class Member(AbstractBaseUser, PermissionsMixin):
    """This is the base model for all members (this includes staffers)"""

    objects = MemberManager()

    primary_key = PrimaryKeyField()

    first_name = models.CharField(max_length=50, null=True)
    last_name = models.CharField(max_length=50, null=True)
    email = models.EmailField(verbose_name="email address", max_length=255, unique=True)
    rfid = RFIDField(verbose_name="RFID")
    image = models.ImageField(
        verbose_name="Profile Picture",
        default="shaka.webp",
        upload_to=get_profile_pic_upload_location,
        blank=True,
    )
    phone_number = PhoneNumberField(unique=False, null=True)

    date_joined = models.DateField(auto_now_add=True)
    date_expires = models.DateField(null=False)

    is_admin = models.BooleanField(default=False)
    group = models.CharField(default="Unset", max_length=30)

    #: This is used by django to determine if users are allowed to login. Leave it, except when banishing someone
    is_active = models.BooleanField(
        default=True
    )  # Use is_active_member to check actual activity
    certifications = models.ManyToManyField(Certification, blank=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["date_expires"]

    @property
    def is_active_member(self):
        """Return true if the member has a valid membership"""
        return self.has_permission("core.is_active_member")

    @property
    def is_staff(self):
        """
        Property that is used by django to determine whether a user is allowed to log in to the admin: i.e. everyone
        """
        return True

    @property
    def edit_profile_url(self):
        return reverse("admin:core_member_change", kwargs={"object_id": self.pk})

    @property
    def view_profile_url(self):
        return reverse("admin:core_member_detail", kwargs={"pk": self.pk})

    def has_name(self):
        """Check whether the name of this member has been set"""
        return self.first_name and self.last_name

    def get_full_name(self):
        """Return the full name if it is know, or 'New Member' if it is not"""
        if self.has_name():
            return f"{self.first_name} {self.last_name}"
        else:
            return "New Member"

    get_full_name.short_description = "Full Name"

    def get_short_name(self):
        # The user is identified by their email address
        return self.first_name

    def get_all_certifications(self):
        all_certs = self.certifications.all()
        return all_certs

    def has_no_certifications(self):
        return len(self.certifications.all()) == 0

    def __str__(self):
        """
        If we know the name of the user, then display their name, otherwise use their email
        """
        if self.has_name():
            return self.get_full_name()
        else:
            return self.email

    def update_admin(self):
        """Updates the admin status of the user in the django system"""
        self.is_admin = self.groups.name == "Admin"

    def expire(self):
        """Expires this member's membership"""
        self.move_to_group("Expired")

    def promote_to_active(self):
        """Move the member to the group of active members"""
        self.move_to_group("Member")

    def extend_membership(self, duration, rfid="", password=""):
        """Add the given amount of time to this member's membership, and optionally update their rfid and password"""

        self.move_to_group("Just Joined")

        if self.date_expires < datetime.date(now()):
            self.date_expires = now() + duration
        else:
            self.date_expires += duration

        if rfid:
            self.rfid = rfid

        if password:
            self.set_password(password)

        return self

    def send_email(self, title, body, from_email, email_host_password):
        """Sends an email to the member"""
        send_mail(
            title,
            body,
            from_email,
            [self.email],
            fail_silently=False,
            auth_user=from_email,
            auth_password=email_host_password,
        )

    def send_membership_email(self, title, body):
        """Send an email to the member from the membership email"""
        self.send_email(
            title,
            body,
            settings.MEMBERSHIP_EMAIL_HOST_USER,
            settings.MEMBERSHIP_EMAIL_HOST_PASSWORD,
        )

    def send_intro_email(self, finish_signup_url):
        """Send the introduction email with the link to finish signing up to the member"""
        title = "Finish Signing Up"
        # get the absolute path equivalent of going up one level and then into the templates directory
        templates_dir = os.path.abspath(
            os.path.join(os.path.dirname(__file__), os.pardir, "templates")
        )
        template_file = open(os.path.join(templates_dir, "emails", "intro_email.txt"))
        template = template_file.read()
        body = template.format(finish_signup_url=finish_signup_url)
        self.send_membership_email(title, body)

    def send_expire_soon_email(self):
        """Send an email warning the member that their membership will soon expire"""
        title = "Excursion Club Membership Expiring Soon!"
        templates_dir = os.path.abspath(
            os.path.join(os.path.dirname(__file__), os.pardir, "templates")
        )
        template_file = open(os.path.join(templates_dir, "emails", "intro_email.txt"))
        template = template_file.read()
        body = template.format(
            member_name=self.get_full_name(), expiration_date=self.date_expires
        )
        self.send_membership_email(title, body)

    def has_module_perms(self, app_label):
        """This is required by django, determine whether the user is allowed to view the app"""
        return True

    def has_permission(self, permission_name):
        """Loop through all the permissions of the group associated with this member to see if they have this one"""
        return self.has_perm(permission_name)

    def move_to_group(self, group_name):
        """
        Convenience function to move a member to a group

        Always use this function since it changes the group and the group shortcut field
        """
        new_group = Group.objects.filter(name=group_name)
        self.groups.set(new_group)
        self.group = str(new_group[0])
        self.save()
Beispiel #3
0
class Gear(models.Model):
    """
    The base model for a piece of gear
    """

    objects = GearManager()

    class Meta:
        verbose_name_plural = "Gear"

    primary_key = PrimaryKeyField()
    rfid = models.CharField(max_length=10, unique=True)
    image = models.ForeignKey(AlreadyUploadedImage, on_delete=models.CASCADE)
    status_choices = [
        (0, "In Stock"
         ),  # Ready and available in the gear sheds, waiting to be used
        (1, "Checked Out"
         ),  # Somebody has it right now, but it should soon be available again
        (
            2, "Broken"
        ),  # It is broken to the point it should not be checked out, waiting for repair
        (
            3, "Missing"
        ),  # Has been checked out for a while, time to yell at a member to give it back
        (4, "Dormant"
         ),  # Missing for very long time: assume it has been lost until found
        (5, "Removed"
         ),  # It is gone, dead and never coming back. Always set manually
    ]
    #: The status determines what transactions the gear can participate in and where it is visible
    status = models.IntegerField(choices=status_choices)

    #: Who currently has this piece of gear. If null, then the gear is not checked out
    checked_out_to = models.ForeignKey(Member,
                                       blank=True,
                                       null=True,
                                       on_delete=models.SET_NULL)

    #: The date at which this gear is due to be returned, null if not checked out
    due_date = models.DateField(blank=True, null=True, default=None)

    geartype = models.ForeignKey(GearType, on_delete=models.CASCADE)

    gear_data = models.CharField(max_length=2000)

    def __str__(self):
        return self.name

    def __getattr__(self, item):
        """
        Allows the values of CustomDataFields stored in GearType to be accessed as if they were attributes of Gear
        """

        gear_data = json.loads(self.__getattribute__('gear_data'))

        if item is None:
            return self
        elif item in gear_data.keys():
            geartype = self.__getattribute__('geartype')
            field = geartype.data_fields.get(name=item)
            return field.get_value(gear_data[item])
        else:
            raise AttributeError(f'No attribute {item} for {repr(self)}!')

    def get_display_gear_data(self):
        """Return the gear data as a simple dict of field_name, field_value"""
        simple_data = {}
        attr_fields = self.geartype.data_fields.all()
        gear_data = json.loads(self.gear_data)
        for field in attr_fields:
            simple_data[field.name] = field.get_str(gear_data[field.name])
        return simple_data

    @property
    def edit_gear_url(self):
        return reverse("admin:core_gear_change", kwargs={"object_id": self.pk})

    def get_extra_fieldset(self, name="Additional Data", classes=('wide', )):
        """Get a fieldset that contains data on how to represent the extra data fields contained in geartype"""
        fieldset = (name, {
            'classes': classes,
            'fields': self.geartype.get_field_names()
        })
        return fieldset

    def get_status(self):
        return self.status_choices[self.status][1]

    @property
    def image_url(self):
        if self.image and hasattr(self.image, 'url'):
            return self.image.url

    @property
    def name(self):
        """
        Auto-generate a name that can (semi-uniquely) identify this piece of gear

        Name will be in the form: <GearType> - <attr 1>, <attr 2>, etc...
        """

        # Get all custom data fields for this data_type, except those that contain a rfid
        attr_fields = self.geartype.data_fields.exclude(data_type='rfid')
        attributes = []
        gear_data = json.loads(self.gear_data)
        for field in attr_fields:
            string = field.get_str(gear_data[field.name])
            if string:
                attributes.append(str(string))

        if attributes:
            attr_string = ", ".join(attributes)
            name = f'{self.geartype.name} - {attr_string}'
        else:
            name = self.geartype.name

        return name

    def get_department(self):
        return self.geartype.department

    get_department.short_description = "Department"

    def is_available(self):
        """Returns True if the gear is available for renting"""
        return True if self.status == 0 else False

    def is_rented_out(self):
        return True if self.status == 1 else False

    def is_active(self):
        """Returns True if the gear is actively in circulation (ie could be checked out in a few days)"""
        if self.status <= 1:
            return True
        else:
            return False

    def is_existent(self):
        """Returns True if the gear has not been removed or lost"""
        if self.status <= 3:
            return True
        else:
            return False
Beispiel #4
0
class Member(AbstractBaseUser, PermissionsMixin):
    """This is the base model for all members (this includes staffers)"""

    objects = MemberManager()
    primary_key = PrimaryKeyField()

    # Personal contact information
    first_name = models.CharField(max_length=50, null=True)
    last_name = models.CharField(max_length=50, null=True)
    email = models.EmailField(verbose_name="email address",
                              max_length=255,
                              unique=True)
    image = models.ImageField(
        verbose_name="Profile Picture",
        default=settings.DEFAULT_IMG,
        upload_to=get_profile_pic_upload_location,
        blank=True,
        null=True,
    )
    phone_number = PhoneNumberField(unique=False, null=True)

    # Emergency contact information
    emergency_contact_name = models.CharField(max_length=100,
                                              verbose_name="Contact Name",
                                              null=True)
    emergency_relation = models.CharField(max_length=50,
                                          verbose_name="Relationship",
                                          null=True)
    emergency_phone = PhoneNumberField(unique=False,
                                       verbose_name="Phone Number",
                                       null=True)
    emergency_email = models.EmailField(unique=False,
                                        verbose_name="Best Email",
                                        null=True)

    # Membership data
    date_joined = models.DateField(auto_now_add=True)
    date_expires = models.DateField(null=False)
    rfid = RFIDField(verbose_name="RFID")
    group = models.CharField(default="Unset", max_length=30)
    is_admin = models.BooleanField(default=False)
    certifications = models.ManyToManyField(Certification, blank=True)

    #: This is used by django to determine if users are allowed to login. Leave it, except when banishing someone
    is_active = models.BooleanField(
        default=True)  # Use is_active_member to check actual activity

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["date_expires"]

    @property
    def is_active_member(self):
        """Return true if the member has a valid membership"""
        return self.has_permission("core.is_active_member")

    @property
    def is_staff(self):
        """
        Property that is used by django to determine whether a user is allowed to log in to the admin: i.e. everyone
        """
        return True

    @property
    def is_staffer(self):
        """Property to check if a member is a excursion staffer or not"""
        return self.group in ['Staff', 'Board', 'Admin']

    @property
    def edit_profile_url(self):
        return reverse("admin:core_member_change",
                       kwargs={"object_id": self.pk})

    @property
    def view_profile_url(self):
        return reverse("admin:core_member_detail", kwargs={"pk": self.pk})

    @property
    def make_staff_url(self):
        return reverse('admin:core_staffer_add', kwargs={'member': self})

    def has_name(self):
        """Check whether the name of this member has been set"""
        return self.first_name and self.last_name

    def get_full_name(self):
        """Return the full name if it is know, or 'New Member' if it is not"""
        if self.has_name():
            return f"{self.first_name} {self.last_name}"
        else:
            return "New Member"

    get_full_name.short_description = "Full Name"

    def get_short_name(self):
        # The user is identified by their email address
        return self.first_name

    def get_all_certifications(self):
        all_certs = self.certifications.all()
        return all_certs

    def has_no_certifications(self):
        return len(self.certifications.all()) == 0

    def __str__(self):
        """
        If we know the name of the user, then display their name, otherwise use their email
        """
        if self.has_name():
            return self.get_full_name()
        else:
            return self.email

    def update_admin(self):
        """Updates the admin status of the user in the django system"""
        self.is_admin = self.groups.name == "Admin"

    def expire(self):
        """Expires this member's membership"""
        self.move_to_group("Expired")

    def promote_to_active(self):
        """Move the member to the group of active members"""
        if self.group == "Staff" or self.group == "Board" or self.group == "Admin":
            print("Member status is already better than member")
        else:
            self.move_to_group("Member")

    def extend_membership(self, duration, rfid="", password=""):
        """Add the given amount of time to this member's membership, and optionally update their rfid and password"""

        self.move_to_group("Just Joined")

        if self.date_expires < datetime.date(now()):
            self.date_expires = now() + duration
        else:
            self.date_expires += duration

        if rfid:
            self.rfid = rfid

        if password:
            self.set_password(password)

        return self

    def send_email(self, title, body, from_email):
        """Sends an email to the member"""
        emailing.send_email([self.email],
                            title,
                            body,
                            from_email=from_email,
                            from_name='Excursion Club',
                            receiver_names=[self.get_full_name()])

    def send_membership_email(self, title, body):
        """Send an email to the member from the membership email"""
        emailing.send_membership_email([self.email],
                                       title,
                                       body,
                                       receiver_names=[self.get_full_name()])

    def send_intro_email(self, finish_signup_url):
        """Send the introduction email with the link to finish signing up to the member"""
        title = "Finish Signing Up"
        template = get_email_template('intro_email')
        body = template.format(finish_signup_url=finish_signup_url)
        self.send_membership_email(title, body)

    def send_expires_soon_email(self):
        """Send an email warning the member that their membership will soon expire"""
        title = "Climbing Club Membership Expiring Soon!"
        template = get_email_template('expire_soon_email')
        body = template.format(member_name=self.get_full_name(),
                               expiration_date=self.date_expires)
        self.send_membership_email(title, body)

    def send_expired_email(self):
        """Send an email warning the member that their membership will soon expire"""
        title = "Climbing Club Membership Expired!"
        template = get_email_template('expired_email')
        body = template.format(member_name=self.get_full_name(),
                               today=self.date_expires)
        self.send_membership_email(title, body)

    def send_missing_gear_email(self, all_gear):
        """Send an email to member that they have gear to return"""
        gear_rows = []
        for gear in all_gear:
            gear_rows.append(
                f"<tr><td>{gear.name}</td><td>{gear.due_date.strftime('%a, %b %d, %Y')}</td></tr>"
            )
        template = get_email_template('missing_gear')
        body = template.format(first_name=self.first_name,
                               gear_rows="".join(gear_rows))
        title = 'Gear Overdue'
        self.send_email(
            title,
            body,
            '*****@*****.**',
        )

    def send_new_staff_email(self, staffer):
        """Sen an email welcoming the member to staff"""
        title = "Welcome to staff!"
        template = get_email_template('new_staffer')
        body = template.format(member_name=self.first_name,
                               finish_url=settings.WEB_BASE +
                               staffer.edit_profile_url,
                               staffer_email=staffer.exc_email)
        self.send_membership_email(title, body)

    def has_module_perms(self, app_label):
        """This is required by django, determine whether the user is allowed to view the app"""
        return True

    def has_permission(self, permission_name):
        """Loop through all the permissions of the group associated with this member to see if they have this one"""
        return self.has_perm(permission_name)

    def move_to_group(self, group_name):
        """
        Convenience function to move a member to a group

        Always use this function since it changes the group and the group shortcut field
        """
        new_group = Group.objects.filter(name=group_name)
        self.groups.set(new_group)
        self.group = str(new_group[0])
        self.save()