예제 #1
0
파일: models.py 프로젝트: osya/tryTen
class Good(models.Model):
    class Meta:
        ordering = (
            '-price',
            'name',
        )
        unique_together = (
            'category',
            'name',
            'price',
        )
        verbose_name = 'good'
        verbose_name_plural = 'goods'

    name = models.CharField(max_length=50, unique=True, verbose_name='Name')
    description = models.TextField()
    category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL, related_name='goods')
    in_stock = models.BooleanField(default=True, db_index=True, verbose_name='In stock')
    price = models.FloatField()
    tags = TaggableManager(blank=True)
    slug = AutoSlugField(
        populate_from=get_slug,
        unique=True
    )
    objects = GoodQuerySet.as_manager()

    def __str__(self):
        return self.name if self.in_stock else f'{self.name} (out of stock)'

    def get_in_stock(self):
        return '+' if self.in_stock else ''

    def get_absolute_url(self):
        return reverse('goods:detail', kwargs={'slug': self.slug})
예제 #2
0
class Post(models.Model):
    # pragma pylint: disable=R0903
    class Meta:
        ordering = ('-created', )
        verbose_name = 'Blog Post'
        verbose_name_plural = 'Blog Posts'

    # pragma pylint: enable=R0903

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    title = models.CharField(max_length=100, unique_for_date='created')
    body = MarkdownField()
    slug = AutoSlugField(populate_from='title', unique=True)
    is_commentable = models.BooleanField(default=True)
    tags = TaggableManager(blank=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    published = models.DateTimeField(null=True, blank=True)
    updated = models.DateTimeField(auto_now=True)

    objects = PostQuerySet.as_manager()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post:detail', kwargs={'slug': self.slug})
예제 #3
0
class Model3D(models.Model):
    creation_date = models.DateTimeField(default=timezone.now)
    search_img = models.ForeignKey('Image',
                                   on_delete=models.DO_NOTHING,
                                   related_name='search_img',
                                   null=True)
    description = models.CharField(max_length=500,
                                   default="Empty",
                                   unique=True)
    name = models.CharField(max_length=200)
    # tags mechanism
    tags = TaggableManager(blank=True)

    class Meta:
        verbose_name_plural = "3DModels"
        ordering = ("-pk", )
        permissions = (("can_tag", "Allow normal user to tag model"), )

    def __str__(self):
        return self.description

    def get_absolute_url(self):
        return reverse(
            "gallery:single_model",
            kwargs={"pk": self.pk},
        )

    def delete(self, *args, **kwargs):
        """Custom delete method to all images of this model"""
        self.search_img = None
        self.save()
        for i in Image.objects.filter(linked_model=self.pk):
            i.delete()
        super().delete(*args, **kwargs)
예제 #4
0
class Issue(TimeStampedModel):
    number = models.PositiveSmallIntegerField(_("number"), unique=True)
    pub_date = models.DateTimeField(_("publication datetime"))
    description = models.TextField(_("description"))
    creator = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=_("creator"),
        on_delete=models.SET_NULL,
        null=True,
    )
    tags = TaggableManager(verbose_name=_("tags"),
                           through=TaggedItem,
                           blank=True)

    class Meta:
        ordering = ["-number"]
        verbose_name = _("issue")
        verbose_name_plural = _("issues")

    def __str__(self):
        return "{}".format(self.number)

    def get_absolute_url(self):
        return reverse("favorites:issue_detail",
                       kwargs={"number": self.number})
예제 #5
0
class Blog(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(editable=True, max_length=255, unique=True)
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    tags = TaggableManager()

    def __str__(self):
        return self.title
예제 #6
0
class Task(TimeStampedModel, MP_Node):

    ROOT_USERNAME = '******'

    labels = TaggableManager(blank=True)
    title = models.TextField()
    info = HTMLField("info", max_length=2000, blank=True)

    complete = models.DateTimeField(blank=True, null=True)

    duration = models.TextField(null=True, default=None)

    priority = models.PositiveSmallIntegerField(
        default=1, validators=[MaxValueValidator(3),
                               MinValueValidator(1)])

    expanded = models.BooleanField(default=True)

    # move: https://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.move
    @classmethod
    def ordered_tasks(cls):

        tasks = list(Task.objects.all())  #filter(expanded=True))

        tree = {}

        task: Task
        for task in tasks:
            pass

        return tasks

    @property
    def start_iso(self):
        if self.complete is None:
            return None
        start = self.complete - datetime.timedelta(minutes=int(self.duration))
        return start.isoformat()

    @property
    def end_iso(self):
        if self.complete is None:
            return None
        end: datetime.datetime = self.complete
        return end.isoformat()

    def __unicode__(self):
        return self.complete.isoformat()
예제 #7
0
class Post(ModelMeta, TimeStampModel):
    author = models.ForeignKey('users.User')
    title = models.CharField(max_length=200)
    slug = AutoSlugField(_('slug'),
                         max_length=50,
                         editable=True,
                         populate_from=('title', ))
    text = MarkdownxField()
    publish = models.BooleanField(default=False)

    tags = TaggableManager()

    _metadata = {'title': 'title', 'author': 'author', 'image': 'get_img_url'}

    class Meta:
        verbose_name_plural = "posts"

    @property
    def formatted_markdown(self):
        return markdownify(self.text)

    def get_absolute_url(self):
        return reverse('blog:detail', kwargs={'pk': self.pk})

    def create_meta_description(self):
        pass
        # TODO
        # description = self.text.split(' ')
        # word_list = description[0:10]
        # string = '\s'.join(e for e in word_list)
        # clean_description = re.sub('[^A-Za-z0-9\s]+', '', string45r)
        # return clean_description

    def get_img_url(self):
        regex = "\!\[.*\]\(([^)]+)"
        urls = re.findall(regex, self.text)
        if len(urls) > 0:
            return urls[0]
        else:
            return ""

    def __str__(self):
        return self.title

    def publish_post(self):
        self.publish = True
        self.save()
예제 #8
0
파일: models.py 프로젝트: osya/todolist
class Todo(models.Model):
    class Meta:
        ordering = ('-created_at', )

    title = models.CharField(max_length=200)
    text = MarkdownField()
    created_at = models.DateTimeField(auto_now_add=True, blank=True)
    tags = TaggableManager(blank=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.PROTECT)

    objects = TodoQuerySet.as_manager()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('todos:detail', kwargs={'pk': self.pk})
예제 #9
0
class Listing(models.Model):
    description = models.CharField(max_length=500, blank=True)
    name = models.CharField(max_length=100, blank=False)
    link = models.URLField(max_length=500, unique=True, blank=True)
    # tags mechanism
    tags = TaggableManager(blank=True)

    class Meta:
        verbose_name_plural = "Listings"
        permissions = (("can_tag", "Allow normal user to tag listing"), )

    def __str__(self):
        return self.link + '(' + self.description + ')'

    def get_absolute_url(self):
        return reverse(
            "listing:single",
            kwargs={"pk": self.pk},
        )
예제 #10
0
class Post(models.Model):
    title = models.CharField(max_length=250, verbose_name='Tytuł')
    slug = models.SlugField(unique=True, max_length=250)
    body = models.TextField(
        verbose_name='Treść',
        help_text=
        'Aby inaczej sformatować tekst, zaznacz fragment tekstu, który chcesz zmienić i kliknij wybraną ikonę.'
    )
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    tags = TaggableManager()
    active = models.BooleanField(default=False)
    image = models.ImageField(
        upload_to='post_image',
        blank=True,
        verbose_name='Miniatura postu',
        help_text=
        'Aby nie łamać praw autorskich, warto skorzystać z darmowych zdjęć na stocksnap.io, unsplash.com lub pexels.com. Warto jednak pamiętać o rozdzielczości'
    )

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('blog:tresc_postu', args=[self.slug])

    def _get_unique_slug(self):
        slug = slugify(self.title)
        unique_slug = slug
        num = 1
        while Post.objects.filter(slug=unique_slug).exists():
            unique_slug = '{}-{}'.format(slug, num)
            num += 1
        return unique_slug

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self._get_unique_slug()
        super().save()
예제 #11
0
class Favorite(TimeStampedModel):
    issue = models.ForeignKey(
        Issue,
        verbose_name=_("issue"),
        on_delete=models.SET_NULL,
        null=True,
        related_name="favorites",
    )
    title = models.CharField(_("title"), max_length=200)
    description = models.TextField(_("description"))
    url = models.URLField(_("url"))
    rank = models.IntegerField(_("rank"), default=0)
    tags = TaggableManager(verbose_name=_("tags"),
                           through=TaggedItem,
                           blank=True)

    class Meta:
        ordering = ["rank", "-created_at"]
        verbose_name = _("favorite")
        verbose_name_plural = _("favorites")

    def __str__(self):
        return self.title
예제 #12
0
class Tag(models.Model):
    post = models.ForeignKey(Post,
                             on_delete=models.CASCADE,
                             related_name='tag')
    tags = TaggableManager(blank=True, help_text=None, verbose_name='')
예제 #13
0
class Profile(models.Model):
    GENDER_CHOICES = (
        ('male', 'Male'),
        ('female', 'Female'),
        ('other', 'Other'),
    )

    YEAR_OF_ENTRANCE = (
        ('2015', '2015'),
        ('2016', '2016'),
        ('2017', '2017'),
        ('2018', '2018'),
        ('2019', '2019'),
    )

    YEAR_OF_GRADUATION = (
        ('2020', '2020'),
        ('2021', '2021'),
        ('2022', '2022'),
        ('2023', '2023'),
        ('2024', '2024'),
    )
    user = models.OneToOneField(User,
                                on_delete=models.CASCADE,
                                related_name='profile')
    gender = models.CharField(max_length=10, choices=GENDER_CHOICES)
    profile_photo = models.ImageField(upload_to='profile_pictures/',
                                      blank=True)
    bio = models.CharField(max_length=145, blank=True)
    interests = TaggableManager(verbose_name='Interests',
                                help_text='football, programming, photography')
    phone_number = models.CharField(max_length=20, blank=True)
    department = models.CharField(max_length=30, blank=True)
    level = models.IntegerField(blank=True, null=True)
    staff_status = models.BooleanField(default=False)
    verified = models.BooleanField(default=False)
    year_of_entrance = models.CharField(max_length=10,
                                        default='2020',
                                        choices=YEAR_OF_ENTRANCE,
                                        blank=True)
    year_of_graduation = models.CharField(max_length=10,
                                          blank=True,
                                          null=True,
                                          choices=YEAR_OF_GRADUATION)
    email_confirmed = models.BooleanField(default=False)
    secret_code = models.CharField(null=True,
                                   blank=True,
                                   max_length=32,
                                   unique=True)

    def __str__(self):
        return f'profile for user {self.user.username}'

    def save(self, *args, **kwargs):
        if self.profile_photo:
            self.thumbnail = make_thumbnail(self.profile_photo, size=(90, 90))
        if not self.secret_code:
            profiles = Profile.objects.all()
            codes = [code.secret_code for code in profiles]
            confirm_new_secret = False
            while not confirm_new_secret:
                new_secret = random.randint(1000000000000000000000000000000,
                                            99999999999999999999999999999999)
                if new_secret not in codes:
                    self.secret_code = new_secret
                    confirm_new_secret = True
        super().save(*args, **kwargs)
예제 #14
0
class Post(RulesModelMixin, auto_prefetch.Model, metaclass=RulesModelBase):
    """Post Model

    A model for Blog Posts

    Args:
        RulesModelMixin ([type]): [description]
        auto_prefetch ([type]): [description]
        metaclass ([type], optional): [description]. Defaults to RulesModelBase.

    Returns:
        Post: A model for Post
    """

    PUBLICATION_CHOICES = [
        ("P", "Published"),
        ("W", "Withdrawn"),
        ("D", "Draft"),
    ]

    FEATURING_CHOICES = [
        ("F", "Featured"),
        ("FB", "Featured Big"),
        ("N", "Not Featured"),
    ]

    LANGUAGE_CHOICES = [
        ("EN", "English"),
        ("FR", "French"),
        ("ML", "Multi Linguistic"),
        ("OL", "Other Language"),
        ("NS", "Not Specified"),
    ]

    author = auto_prefetch.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="post_author",
        help_text="Post author",
    )
    title = models.CharField(max_length=200, help_text="Post title")
    featured_title = models.CharField(max_length=27,
                                      default="",
                                      blank=True,
                                      help_text="Featured post title")
    body = MarkdownxField(help_text="Post main content", blank=True)

    image = models.ImageField(null=True,
                              blank=True,
                              upload_to="images/post/",
                              help_text="Post image")
    description = models.TextField(help_text="Post description")
    slug = models.SlugField(unique=True, max_length=200, help_text="Post slug")
    categories = models.ManyToManyField("blog.Category",
                                        through="PostCatsLink",
                                        help_text="Post categories")
    tags = TaggableManager(blank=True, through=CustomTaggedItem)
    series = auto_prefetch.ForeignKey(
        "blog.Series",
        on_delete=models.CASCADE,
        related_name="post_series",
        help_text="Post series",
        blank=True,
        null=True,
    )
    order_in_series = models.PositiveIntegerField(
        default=0, help_text="Post order in its series")
    created_date = models.DateTimeField(default=timezone.now,
                                        help_text="Creation date")
    mod_date = models.DateTimeField(auto_now=True,
                                    help_text="Last modification")
    pub_date = models.DateTimeField(blank=True,
                                    null=True,
                                    help_text="Publication date")
    publication_state = models.CharField(
        max_length=25,
        verbose_name="Publication",
        choices=PUBLICATION_CHOICES,
        default="D",
        help_text="Post publication state",
    )
    withdrawn = models.BooleanField(default=False,
                                    help_text="Is Post withdrawn")
    featuring_state = models.CharField(
        max_length=25,
        verbose_name="Featuring",
        choices=FEATURING_CHOICES,
        default="N",
        help_text="Featuring state",
    )
    language = models.CharField(
        max_length=25,
        verbose_name="Language",
        choices=LANGUAGE_CHOICES,
        default="EN",
        help_text="What's the main language",
    )
    is_outdated = models.BooleanField(default=False,
                                      help_text="Is Post content's outdated")
    url_to_article = models.URLField(
        default="", blank=True, help_text="Url to page that inspired the Post")
    url_to_article_title = models.CharField(
        max_length=200,
        default="",
        blank=True,
        help_text="What will be shown as url name",
    )
    clicks = models.IntegerField(
        default=0, help_text="How many times the Post has been seen")
    rnd_choice = models.IntegerField(
        default=0,
        help_text="How many times the Post has been randomly chosen")
    history = HistoricalRecords()
    is_removed = models.BooleanField("is removed",
                                     default=False,
                                     db_index=True,
                                     help_text=("Soft delete"))
    needs_reviewing = models.BooleanField(default=False,
                                          help_text=("Needs reviewing"))
    enable_comments = models.BooleanField(default=True)

    objects: PostManager = PostManager()

    class Meta:
        """Meta class for Post Model"""

        ordering = ["-pub_date"]
        get_latest_by = ["id"]
        rules_permissions = {
            "add": rules.is_superuser,
            "update": rules.is_superuser,
        }

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            max_length = Post._meta.get_field("slug").max_length
            self.slug = orig = slugify(self.title)[:max_length]
            for x in itertools.count(2):
                if (self.pk and Post.objects.filter(
                        Q(slug=self.slug),
                        Q(author=self.author),
                        Q(id=self.pk),
                ).exists()):
                    break
                if not Post.objects.filter(slug=self.slug).exists():
                    break

                # Truncate & Minus 1 for the hyphen.
                self.slug = "%s-%d" % (orig[:max_length - len(str(x)) - 1], x)
        return super().save(*args, **kwargs)

    def save_without_historical_record(self, *args, **kwargs):
        self.skip_history_when_saving = True
        try:
            ret = self.save(*args, **kwargs)
        finally:
            del self.skip_history_when_saving
        return ret

    def get_absolute_url(self):
        return reverse("blog:post_detail", kwargs={"slug": self.slug})

    def get_absolute_update_url(self):
        return reverse("blog:post_edit", kwargs={"slug": self.slug})

    def get_absolute_needs_review_url(self):
        return reverse("blog:post_needs_review", kwargs={"slug": self.slug})

    def get_absolute_delete_url(self):
        return reverse("blog:post_remove", kwargs={"slug": self.slug})

    def get_absolute_publish_url(self):
        return reverse("blog:post_publish", kwargs={"slug": self.slug})

    def get_absolute_publish_withdrawn_url(self):
        return reverse("blog:post_publish_withdrawn",
                       kwargs={"slug": self.slug})

    def get_absolute_clone_url(self):
        return reverse("blog:clone_post", kwargs={"slug": self.slug})

    def get_absolute_admin_update_url(self):
        return reverse("admin:blog_post_change", kwargs={"object_id": self.pk})

    def publish(self):
        self.pub_date = timezone.now()
        self.publication_state = "P"
        self.withdrawn = False
        self.save()

    def publish_withdrawn(self):
        self.pub_date = timezone.now()
        self.publication_state = "W"
        self.withdrawn = True
        self.save()

    def needs_review(self):
        self.needs_reviewing = True
        self.save()

    def clicked(self):
        self.clicks += 1
        self.save_without_historical_record(update_fields=["clicks"])

    def rnd_chosen(self):
        self.rnd_choice += 1
        self.save_without_historical_record(update_fields=["rnd_choice"])

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

    def remove(self):
        self.is_removed = True
        self.save()

    # Create a property that returns the markdown instead
    @property
    def formatted_markdown(self):
        return markdownify(self.body)

    @property
    def is_scheduled(self) -> bool:
        now = timezone.now()
        if not self.pub_date:
            return False

        return self.pub_date >= now

    @property
    def author_name(self):
        return self.author.username

    @property
    def get_tags(self):
        return self.tags.filter(withdrawn=False)

    @property
    def get_admin_tags(self):
        return self.tags.all()

    @property
    def get_featured_cat(self):
        for post_cat in PostCatsLink.objects.filter(
                post_id=self.pk, category__is_removed=False,
                featured_cat=True).select_related("post", "category"):
            return post_cat

    @property
    def featured_cat_title(self):
        for post_cat in PostCatsLink.objects.filter(
                post_id=self.pk, category__is_removed=False,
                featured_cat=True).select_related("post", "category"):
            return post_cat.category.full_title

    def get_index_view_url(self):
        content_type = ContentType.objects.get_for_model(self.__class__)
        return reverse("%s:%s_list" %
                       (content_type.app_label, content_type.model))
예제 #15
0
class Photo(index.Indexed, models.Model):

    REVIEW_STATUS_SUBMITTING = -1
    REVIEW_STATUS_SUBMITTED = 0
    REVIEW_STATUS_PUBLIC = 1
    REVIEW_STATUS_ARCHIVED = 2
    REVIEW_STATUSES = (
        (REVIEW_STATUS_SUBMITTED, 'To be reviewed (not public)'),
        (REVIEW_STATUS_PUBLIC, 'Public'),
        (REVIEW_STATUS_ARCHIVED, 'Archived (not public)'),
        (REVIEW_STATUS_SUBMITTING, 'Incomplete submission'),
    )

    FEELING_POSITIVE = -1
    FEELING_NEUTRAL = 0
    FEELING_NEGATIVE = 1
    FEELINGS = (
        (FEELING_POSITIVE, 'Positive'),
        (FEELING_NEUTRAL, 'Neutral'),
        (FEELING_NEGATIVE, 'Negative'),
    )

    MONTHS = [(i + 1, name) for i, name in enumerate(calendar.month_name[1:])]

    photographer = models.ForeignKey(
        Photographer,
        on_delete=models.SET_NULL,
        null=True, blank=True, default=None
    )

    # public = models.BooleanField(default=False)
    # image = models.ImageField(upload_to='photos', blank=True, null=True)
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='+'
    )

    legacy_categories = models.TextField(
        'Legacy categories, for reference only',
        blank=True, default=''
    )

    description = models.TextField(blank=True, default='')
    comments = models.TextField(
        'Internal comments', blank=True, null=True
    )

    created_at = models.DateTimeField(
        auto_now_add=True
    )
    updated_at = models.DateTimeField(auto_now=True)

    taken_year = models.IntegerField(
        'Year (photo content)', blank=True, null=True, default=None)
    taken_month = models.IntegerField(
        'Month (photo content)',
        choices=MONTHS,
        blank=True, null=True, default=None
    )
    taken_day = models.IntegerField(
        'Day (photo content)', blank=True, null=True, default=None
    )

    location = models.PointField(blank=True, null=True)

    review_status = models.IntegerField(choices=REVIEW_STATUSES, default=0)

    subcategories = models.ManyToManyField(PhotoSubcategory, blank=True)

    # data captured by the submission form
    author_focus_keywords = models.CharField(
        max_length=150,
        blank=True, null=True, default=None,
        help_text='Author\'s main focus in a few keywords'
    )
    author_focus = models.TextField(
        blank=True, default='', help_text='Author\'s main focus'
    )
    author_feeling_category = models.IntegerField(
        choices=FEELINGS,
        blank=True, null=True, default=None,
        help_text='Author\'s feeling about this photo'
    )
    author_feeling_keywords = models.CharField(
        max_length=100,
        blank=True, default='',
        help_text='Keyword describing author\'s feelings about this photo'
    )
    author_reason = models.TextField(
        'Motivation',
        blank=True, default='',
        help_text='Why did the author take this picture?',
        max_length=500,
    )

    reference_number = models.CharField(
        'Reference number', max_length=20, blank=True, default='',
    )

    tags = TaggableManager(through=PhotoTag, blank=True)

    panels = [
        SnippetChooserPanel('photographer'),
        FieldPanel('review_status'),
        FieldPanel('number'),
        ImageChooserPanel('image'),
        FieldPanel('subcategories'),
        FieldPanel('description'),
        FieldPanel('comments'),
        FieldPanel('date'),
        FieldPanel('location'),
        FieldPanel('tags'),
    ]

    search_fields = [
        index.FilterField('id'),
        index.FilterField('review_status'),
        # index.FilterField('subcategories__pk'),
        # index.SearchField('subcategories__pk'),
        # index.FilterField('photosubcategory_id'),
        index.FilterField('image_id'),
        index.SearchField('description'),
        index.SearchField('tags__name'),
        #         index.RelatedFields('subcategories', [
        #             index.FilterField('id'),
        #         ]),
        index.RelatedFields('tags', [
            index.SearchField('name'),
        ]),
        # two filters with the content...
        # this one is mandatory for all types of backends (filter())
        index.FilterField('photosubcategory_id'),
        # this one is mandatory for default backend (facet())
        index.FilterField('subcategories__pk'),
    ]

    def photosubcategory_id(self):
        """
        https://stackoverflow.com/questions/43082438/how-do-you-filter-search-results-in-wagtail-based-on-a-manytomanyfield
        """
        return list(self.subcategories.all().values_list('id', flat=True))

    def subcategories__pk(self):
        return self.photosubcategory_id()

    class Meta:
        ordering = ['-created_at']

#     def __init__(self, *args, **kwargs):
#         super(Photo, self).__init__(*args, **kwargs)
#         print(self.pk)

    def __str__(self):
        # TODO: find a better way to show pictures on the snippet list page
        # than embedding html tag here.
        return mark_safe('[{}] {}: {}<br>{}'.format(
            self.review_status_label,
            self.photographer, self.title,
            self.get_image_tag('height-50')
        ))

    def save(self, *args, **kwargs):
        if not self.reference_number:
            import time
            self.reference_number = ('%X' % int(time.time())).replace('0', 'X')
        super().save(*args, **kwargs)

    def location_nw(self):
        return get_nw_string_from_point(self.location)

    @property
    def taken_month_name(self):
        from calendar import month_name
        ret = month_name[self.taken_month] if self.taken_month else ''
        return ret

    @property
    def review_status_label(self):
        ret = dict(self.REVIEW_STATUSES)[self.review_status]
        return ret

    @property
    def title(self):
        '''Derives a title from self.description'''
        ret = self.description or ''

        max_len = 50

        if len(ret) > max_len:
            # heuristics to keep smallest meaningful beginning of the desc.
            ret = re.sub(r'\(.*?\)', '', ret)
            ret = re.sub(r'( -|--).*$', '', ret)
            ret = re.sub(r'[.,;:].*$', '', ret)
            ret = re.sub(r'(([A-Z]\w*\b\s*){2,}).*$', r'\1', ret)
            ret = ret.strip()

            if len(ret) > max_len:
                # still too long, truncate and add the ellipsis
                ret = ret[:max_len] + '...'

        return ret

    def get_image_tag(self, specs='height-500'):
        '''
        Returns an html <image> tag for the related image.
        See Wagtail get_rendition_or_not_found() for valid specs.
        '''
        ret = ''

        if self.image:
            rendition = get_rendition_or_not_found(self.image, specs)
            # Remove height and width to keep things responsive.
            # they will be set by CSS.
            ret = re.sub(r'(?i)(height|width)=".*?"', '', rendition.img_tag())

        return ret

    def get_json_dic(self, imgspecs=None, geo_only=False):
        '''Returns a python dictionary representing this instance
        This dictionary will be converted into javascript by the web API
        '''
        p = self

        # TODO: should be dynamic, based on client (smaller for mobile devices)
        type_slug = 'photos'

        location = None
        if p.location:
            location = [p.location.y, p.location.x]

        if geo_only:
            ret = OrderedDict([
                ['id', str(p.pk)],
                ['type', type_slug],
                ['attributes', {
                    'location': location,
                }]
            ])
        else:
            ret = OrderedDict([
                ['id', str(p.pk)],
                ['type', type_slug],
                ['attributes', {
                    'title': p.title,
                    'description': p.description,
                    'location': location,
                    'image': self.get_image_tag(imgspecs),
                    # TODO: don't hard-code this!
                    'url': '/{}/{}'.format(type_slug, p.pk),
                    'taken_year': p.taken_year,
                    'taken_month': p.taken_month,
                    'taken_month_name': p.taken_month_name,
                    'taken_day': p.taken_day,
                    'created_at': p.created_at,
                }]
            ])

        return ret

    def get_absolute_url(self):
        from django.urls import reverse
        return reverse('photo-view', args=[str(self.pk)])
예제 #16
0
class PageModel(models.Model):
    class_name = models.CharField(max_length=200, blank=True)
    title = models.CharField(max_length=200)
    content = RichTextField(null=True, blank=True)
    slug = models.SlugField(unique=True, db_index=True, blank=True, null=True)
    site = models.ForeignKey(Site,
                             related_name="site_page",
                             on_delete=models.CASCADE,
                             null=True,
                             blank=True)
    owner = models.ForeignKey(Member,
                              null=True,
                              blank=True,
                              on_delete=models.CASCADE,
                              related_name="page_owner",
                              verbose_name='owner')
    created_date = models.DateTimeField(db_index=True,
                                        default=datetime.datetime.now)
    is_published = models.BooleanField(default=False, db_index=True)
    is_preview = models.BooleanField(default=False, db_index=True)
    banner_image_1 = models.ImageField(
        upload_to='cp/user_uploads/banner_images/', null=True, blank=True)
    banner_image_2 = models.ImageField(
        upload_to='cp/user_uploads/banner_images/', null=True, blank=True)
    banner_image_3 = models.ImageField(
        upload_to='cp/user_uploads/banner_images/', null=True, blank=True)
    meta_description = models.CharField(max_length=1000,
                                        default="",
                                        blank=True)
    meta_keyword = TaggableManager(blank=True)
    page_view = models.IntegerField(default=0)

    def __str__(self):
        return self.title.title()

    def get_banner_image_1_url(self):
        return ("/media/%s" % self.banner_image_1)

    def get_banner_image_2_url(self):
        return ("/media/%s" % self.banner_image_2)

    def get_banner_image_3_url(self):
        return ("/media/%s" % self.banner_image_3)

    def get_page_url(self):
        return "/%s/" % (self.slug)

    def get_absolute_url(self):
        return "/%s/" % (self.slug)

    def get_edit_url(self):
        return "%s" % (reverse('cms:page_edit_delete',
                               kwargs={
                                   'action': 'edit',
                                   'pk': self.pk
                               }))

    def get_delete_url(self):
        return "%s" % (reverse('cms:page_edit_delete',
                               kwargs={
                                   'action': 'delete',
                                   'pk': self.pk
                               }))

    def get_class_name(self):
        return self.class_name
예제 #17
0
class File(models.Model):
    """Model representing a File """
    name = models.CharField(max_length=200, verbose_name='Τίτλος')
    summary = models.TextField(max_length=1000,
                               help_text='Περιγραφή του αρχείου',
                               verbose_name='Περιγραφή')

    slug = AutoSlugField(populate_from='name',
                         unique=True,
                         null=True,
                         default=None)

    # Foreign Key used because a file can only have one author, but authors can have multiple files
    # Author as a string rather than object because it hasn't been declared yet in the file

    # Date of uploading the file #
    dateCreated = models.DateTimeField(auto_now_add=True)

    category = models.ForeignKey(Category,
                                 help_text='Επιλέξτε κατηγορία',
                                 verbose_name='Κατηγορία',
                                 on_delete=models.CASCADE)
    area = models.ManyToManyField(Area,
                                  help_text='Επιλέξτε Επιστημονική κατηγορία',
                                  verbose_name='Επιστημονική '
                                  'κατηγορία')
    tags = TaggableManager(blank=True)

    file = models.FileField(upload_to='files',
                            null=True,
                            verbose_name='Αρχείο')
    thumbnail = models.ImageField(upload_to='thumbnail',
                                  null=True,
                                  verbose_name='Εικόνα αρχείου')

    uploader = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)

    author = models.CharField(max_length=100,
                              help_text='Δημιουργός',
                              verbose_name='Δημιουργός')
    author_email = models.CharField(max_length=100,
                                    help_text='email δημιουργού',
                                    verbose_name='Email δημιουργού')
    allow_comments = models.BooleanField('allow comments', default=True)

    ratings = GenericRelation(Rating, related_query_name='foos')

    def __str__(self):
        """String for representing the Model object."""
        return self.name

    def get_absolute_url(self):
        return reverse('file_detail', kwargs={'slug': self.slug})

    def get_comments(self):

        return Comment.objects.all().filter(object_pk=self.pk).count()

    def comm(self):
        aggregate = File.objects.aggregate(comment_count=File('comments'))
        return aggregate['comment_count'] + 1

    class Meta:
        verbose_name = 'Αρχείο'
        verbose_name_plural = 'Αρχεία'
예제 #18
0
class Article(models.Model):
    class_name = models.CharField(max_length=200, blank=True)
    category = models.ManyToManyField(Category,
                                      related_name="article_category",
                                      blank=True)
    slug = models.SlugField(max_length=200,
                            unique=True,
                            db_index=True,
                            blank=True,
                            null=True)
    tags = TaggableManager(blank=True)
    title = models.CharField(max_length=200)
    lead_in = models.CharField(max_length=1000, default="", blank=True)
    content = RichTextField(null=True, blank=True)
    site = models.ForeignKey(Site,
                             related_name="article_site",
                             on_delete=models.CASCADE,
                             null=True,
                             blank=True)
    author = models.ForeignKey(Member,
                               null=True,
                               blank=True,
                               on_delete=models.CASCADE,
                               related_name="article_author",
                               verbose_name='author')
    owner = models.ForeignKey(Member,
                              null=True,
                              blank=True,
                              on_delete=models.CASCADE,
                              related_name="article_owner",
                              verbose_name='owner')
    created_date = models.DateTimeField(db_index=True,
                                        default=datetime.datetime.now)
    published_date = models.DateTimeField(db_index=True, null=True, blank=True)
    is_published = models.BooleanField(default=True, db_index=True)
    is_featured = models.BooleanField(default=False, db_index=True)
    is_preview = models.BooleanField(default=False, db_index=True)
    featured_image = models.ImageField(
        upload_to='cp/user_uploads/featured_images/', null=True, blank=True)
    page_view = models.PositiveIntegerField(default=0)

    def __str__(self):
        return self.title.title()

    def get_all_tags(self):
        return self.tags.all()

    def get_image_url(self):
        return "%s" % ("/media/%s" % self.featured_image)

    def get_article_url(self):
        return "%s" % (reverse('blog_detail',
                               kwargs={
                                   'kategori': self.category.all()[0].slug,
                                   'slug': self.slug
                               }))

    def get_edit_url(self):
        return "%s" % (reverse('cms:article_edit_delete',
                               kwargs={
                                   'action': 'edit',
                                   'pk': self.pk
                               }))

    def get_delete_url(self):
        return "%s" % (reverse('cms:article_edit_delete',
                               kwargs={
                                   'action': 'delete',
                                   'pk': self.pk
                               }))

    def get_class_name(self):
        return self.class_name
예제 #19
0
class Document(ModelIndexable):
    """A unified document such as a letter or legal document that
    appears on one or more fragments."""

    id = models.AutoField("PGPID", primary_key=True)
    fragments = models.ManyToManyField(
        Fragment, through="TextBlock", related_name="documents"
    )
    description = models.TextField(blank=True)
    doctype = models.ForeignKey(
        DocumentType,
        blank=True,
        on_delete=models.SET_NULL,
        null=True,
        verbose_name="Type",
        help_text='Refer to <a href="%s" target="_blank">PGP Document Type Guide</a>'
        % settings.PGP_DOCTYPE_GUIDE,
    )
    tags = TaggableManager(blank=True, related_name="tagged_document")
    languages = models.ManyToManyField(
        LanguageScript, blank=True, verbose_name="Primary Languages"
    )
    secondary_languages = models.ManyToManyField(
        LanguageScript, blank=True, related_name="secondary_document"
    )
    language_note = models.TextField(
        blank=True, help_text="Notes on diacritics, vocalisation, etc."
    )
    notes = models.TextField(blank=True)
    created = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)
    needs_review = models.TextField(
        blank=True,
        help_text="Enter text here if an administrator needs to review this document.",
    )
    old_pgpids = ArrayField(models.IntegerField(), null=True, verbose_name="Old PGPIDs")

    PUBLIC = "P"
    STATUS_PUBLIC = "Public"
    SUPPRESSED = "S"
    STATUS_SUPPRESSED = "Suppressed"
    STATUS_CHOICES = (
        (PUBLIC, STATUS_PUBLIC),
        (SUPPRESSED, STATUS_SUPPRESSED),
    )
    PUBLIC_LABEL = "Public"
    #: status of record; currently choices are public or suppressed
    status = models.CharField(
        max_length=2,
        choices=STATUS_CHOICES,
        default=PUBLIC,
        help_text="Decide whether a document should be publicly visible",
    )

    # preliminary date fields so dates can be pulled out from descriptions
    doc_date_original = models.CharField(
        "Date on document (original)",
        help_text="explicit date on the document, in original format",
        blank=True,
        max_length=255,
    )
    CALENDAR_HIJRI = "h"
    CALENDAR_KHARAJI = "k"
    CALENDAR_SELEUCID = "s"
    CALENDAR_ANNOMUNDI = "am"
    CALENDAR_CHOICES = (
        (CALENDAR_HIJRI, "Hijrī"),
        (CALENDAR_KHARAJI, "Kharājī"),
        (CALENDAR_SELEUCID, "Seleucid"),
        (CALENDAR_ANNOMUNDI, "Anno Mundi"),
    )
    doc_date_calendar = models.CharField(
        "Calendar",
        max_length=2,
        choices=CALENDAR_CHOICES,
        help_text="Calendar according to which the document gives a date: "
        + "Hijrī (AH); Kharājī (rare - mostly for fiscal docs); "
        + "Seleucid (sometimes listed as Minyan Shetarot); Anno Mundi (Hebrew calendar)",
        blank=True,
    )
    doc_date_standard = models.CharField(
        "Document date (standardized)",
        help_text="CE date (convert to Julian before 1582, Gregorian after 1582). "
        + "Use YYYY, YYYY-MM, YYYY-MM-DD format when possible",
        blank=True,
        max_length=255,
    )

    footnotes = GenericRelation(Footnote, related_query_name="document")

    log_entries = GenericRelation(LogEntry, related_query_name="document")

    # NOTE: default ordering disabled for now because it results in duplicates
    # in django admin; see admin for ArrayAgg sorting solution
    class Meta:
        pass
        # abstract = False
        # ordering = [Least('textblock__fragment__shelfmark')]

    def __str__(self):
        return f"{self.shelfmark or '??'} (PGPID {self.id or '??'})"

    @staticmethod
    def get_by_any_pgpid(pgpid):
        """Find a document by current or old pgpid"""
        return Document.objects.filter(
            models.Q(id=pgpid) | models.Q(old_pgpids__contains=[pgpid])
        ).first()

    @property
    def shelfmark(self):
        """shelfmarks for associated fragments"""
        # access via textblock so we follow specified order,
        # use dict keys to ensure unique
        return " + ".join(
            dict.fromkeys(
                block.fragment.shelfmark
                for block in self.textblock_set.all()
                if block.certain  # filter locally instead of in the db
            )
        )

    @property
    def certain_join_shelfmarks(self):
        return list(
            dict.fromkeys(
                block.fragment.shelfmark
                for block in self.textblock_set.filter(certain=True)
            ).keys()
        )

    # NOTE: not currently used; remove or revise if this remains unused
    @property
    def shelfmark_display(self):
        """First shelfmark plus join indicator for shorter display."""
        # NOTE preliminary pending more discussion and implementation of #154:
        # https://github.com/Princeton-CDH/geniza/issues/154
        certain = self.certain_join_shelfmarks
        if not certain:
            return None
        return certain[0] + (" + …" if len(certain) > 1 else "")

    @property
    def collection(self):
        """collection (abbreviation) for associated fragments"""
        # use set to ensure unique; sort for reliable output order
        return ", ".join(
            sorted(
                set(
                    [
                        block.fragment.collection.abbrev
                        for block in self.textblock_set.all()
                        if block.fragment.collection
                    ]
                )
            )
        )

    def all_languages(self):
        return ", ".join([str(lang) for lang in self.languages.all()])

    all_languages.short_description = "Language"

    def all_secondary_languages(self):
        return ",".join([str(lang) for lang in self.secondary_languages.all()])

    all_secondary_languages.short_description = "Secondary Language"

    def all_tags(self):
        return ", ".join(t.name for t in self.tags.all())

    all_tags.short_description = "tags"

    def alphabetized_tags(self):
        return self.tags.order_by(Lower("name"))

    def is_public(self):
        """admin display field indicating if doc is public or suppressed"""
        return self.status == self.PUBLIC

    is_public.short_description = "Public"
    is_public.boolean = True
    is_public.admin_order_field = "status"

    def get_absolute_url(self):
        return reverse("corpus:document", args=[str(self.id)])

    @property
    def permalink(self):
        # generate permalink without language url so that all versions
        # have the same link and users will be directed preferred language
        # - get current active language, or default langue if not active
        lang = get_language() or settings.LANGUAGE_CODE
        return absolutize_url(self.get_absolute_url().replace(f"/{lang}/", "/"))

    def iiif_urls(self):
        """List of IIIF urls for images of the Document's Fragments."""
        return list(
            dict.fromkeys(
                filter(None, [b.fragment.iiif_url for b in self.textblock_set.all()])
            )
        )

    def iiif_images(self):
        """List of IIIF images and labels for images of the Document's Fragments."""
        iiif_images = []
        for b in self.textblock_set.all():
            frag_images = b.fragment.iiif_images()
            if frag_images is not None:
                images, labels = frag_images
                iiif_images += [
                    {"image": img, "label": labels[i]} for i, img in enumerate(images)
                ]

        return iiif_images

    def fragment_urls(self):
        """List of external URLs to view the Document's Fragments."""
        return list(
            dict.fromkeys(
                filter(None, [b.fragment.url for b in self.textblock_set.all()])
            )
        )

    def has_transcription(self):
        """Admin display field indicating if document has a transcription."""
        return any(note.has_transcription() for note in self.footnotes.all())

    has_transcription.short_description = "Transcription"
    has_transcription.boolean = True
    has_transcription.admin_order_field = "footnotes__content"

    def has_image(self):
        """Admin display field indicating if document has a IIIF image."""
        return any(self.iiif_urls())

    has_image.short_description = "Image"
    has_image.boolean = True
    has_image.admin_order_field = "textblock__fragment__iiif_url"

    @property
    def title(self):
        """Short title for identifying the document, e.g. via search."""
        return f"{self.doctype or _('Unknown type')}: {self.shelfmark or '??'}"

    def editions(self):
        """All footnotes for this document where the document relation includes
        edition; footnotes with content will be sorted first."""
        return self.footnotes.filter(doc_relation__contains=Footnote.EDITION).order_by(
            "content", "source"
        )

    def digital_editions(self):
        """All footnotes for this document where the document relation includes
        edition AND the footnote has content."""
        return (
            self.footnotes.filter(doc_relation__contains=Footnote.EDITION)
            .filter(content__isnull=False)
            .order_by("content", "source")
        )

    def editors(self):
        """All unique authors of digital editions for this document."""
        return Creator.objects.filter(
            source__footnote__doc_relation__contains=Footnote.EDITION,
            source__footnote__content__isnull=False,
            source__footnote__document=self,
        ).distinct()

    def sources(self):
        """All unique sources attached to footnotes on this document."""
        return Source.objects.filter(footnote__document=self).distinct()

    def attribution(self):
        """Generate a tuple of three attribution components for use in IIIF manifests
        or wherever images/transcriptions need attribution."""
        # keep track of unique attributions so we can include them all
        extra_attrs_set = set()
        for url in self.iiif_urls():
            remote_manifest = IIIFPresentation.from_url(url)
            # CUDL attribution has some variation in tags;
            # would be nice to preserve tagged version,
            # for now, ignore tags so we can easily de-dupe
            try:
                extra_attrs_set.add(strip_tags(remote_manifest.attribution))
            except AttributeError:
                # attribution is optional, so ignore if not present
                pass
        pgp = _("Princeton Geniza Project")
        # Translators: attribution for local IIIF manifests
        attribution = _("Compilation by %(pgp)s." % {"pgp": pgp})
        if self.has_transcription():
            # Translators: attribution for local IIIF manifests that include transcription
            attribution = _("Compilation and transcription by %(pgp)s." % {"pgp": pgp})
        # Translators: manifest attribution note that content from other institutions may have restrictions
        additional_restrictions = _("Additional restrictions may apply.")

        return (
            attribution,
            additional_restrictions,
            extra_attrs_set,
        )

    @classmethod
    def total_to_index(cls):
        # quick count for parasolr indexing (don't do prefetching just to get the total!)
        return cls.objects.count()

    @classmethod
    def items_to_index(cls):
        """Custom logic for finding items to be indexed when indexing in
        bulk."""
        # NOTE: some overlap with prefetching used for django admin
        return (
            Document.objects.select_related("doctype")
            .prefetch_related(
                "tags",
                "languages",
                "footnotes",
                "footnotes__source",
                "footnotes__source__authorship",
                "footnotes__source__authorship__creator",
                "footnotes__source__source_type",
                "footnotes__source__languages",
                "log_entries",
                Prefetch(
                    "textblock_set",
                    queryset=TextBlock.objects.select_related(
                        "fragment", "fragment__collection"
                    ),
                ),
            )
            .distinct()
        )

    def index_data(self):
        """data for indexing in Solr"""
        index_data = super().index_data()

        # get fragments via textblocks for correct order
        # and to take advantage of prefetching
        fragments = [tb.fragment for tb in self.textblock_set.all()]
        index_data.update(
            {
                "pgpid_i": self.id,
                "type_s": str(self.doctype) if self.doctype else _("Unknown type"),
                "description_t": self.description,
                "notes_t": self.notes or None,
                "needs_review_t": self.needs_review or None,
                "shelfmark_ss": self.certain_join_shelfmarks,
                # library/collection possibly redundant?
                "collection_ss": [str(f.collection) for f in fragments],
                "tags_ss_lower": [t.name for t in self.tags.all()],
                "status_s": self.get_status_display(),
                "old_pgpids_is": self.old_pgpids,
                "language_code_ss": [lang.iso_code for lang in self.languages.all()],
            }
        )

        # count scholarship records by type
        footnotes = self.footnotes.all()
        counts = defaultdict(int)
        transcription_texts = []

        # dict of sets of relations; keys are each source attached to any footnote on this document
        source_relations = defaultdict(set)

        for fn in footnotes:
            # if this is an edition/transcription, try to get plain text for indexing
            if Footnote.EDITION in fn.doc_relation and fn.content:
                plaintext = fn.content_text()
                if plaintext:
                    transcription_texts.append(plaintext)
            # add any doc relations to this footnote's source's set in source_relations
            source_relations[fn.source] = source_relations[fn.source].union(
                fn.doc_relation
            )

        # flatten sets of relations by source into a list of relations
        for relation in list(chain(*source_relations.values())):
            # add one for each relation in the flattened list
            counts[relation] += 1

        index_data.update(
            {
                "num_editions_i": counts[Footnote.EDITION],
                "num_translations_i": counts[Footnote.TRANSLATION],
                "num_discussions_i": counts[Footnote.DISCUSSION],
                # count each unique source as one scholarship record
                "scholarship_count_i": self.sources().count(),
                # preliminary scholarship record indexing
                # (may need splitting out and weighting based on type of scholarship)
                "scholarship_t": [fn.display() for fn in self.footnotes.all()],
                # text content of any transcriptions
                "transcription_t": transcription_texts,
            }
        )

        last_log_entry = self.log_entries.last()
        if last_log_entry:
            index_data["input_year_i"] = last_log_entry.action_time.year
            # TODO: would be nice to use full date to display year
            # instead of indexing separately
            # (may require parasolr datetime conversion support? or implement
            # in local queryset?)
            index_data[
                "input_date_dt"
            ] = last_log_entry.action_time.isoformat().replace("+00:00", "Z")

        return index_data

    # define signal handlers to update the index based on changes
    # to other models
    index_depends_on = {
        "fragments": {
            "post_save": DocumentSignalHandlers.related_save,
            "pre_delete": DocumentSignalHandlers.related_delete,
        },
        "tags": {
            "post_save": DocumentSignalHandlers.related_save,
            "pre_delete": DocumentSignalHandlers.related_delete,
        },
        "doctype": {
            "post_save": DocumentSignalHandlers.related_save,
            "pre_delete": DocumentSignalHandlers.related_delete,
        },
        "textblock_set": {
            "post_save": DocumentSignalHandlers.related_save,
            "pre_delete": DocumentSignalHandlers.related_delete,
        }
        # footnotes and sources, when we include editors/translators
        # script+language when/if included in index data
    }

    def merge_with(self, merge_docs, rationale, user=None):
        """Merge the specified documents into this one. Combines all
        metadata into this document, adds the merged documents into
        list of old PGP IDs, and creates a log entry documenting
        the merge, including the rationale."""

        # initialize old pgpid list if previously unset
        if self.old_pgpids is None:
            self.old_pgpids = []

        # if user is not specified, log entry will be associated with
        # script and document will be flagged for review
        script = False
        if user is None:
            user = User.objects.get(username=settings.SCRIPT_USERNAME)
            script = True

        description_chunks = [self.description]
        language_notes = [self.language_note] if self.language_note else []
        notes = [self.notes] if self.notes else []
        needs_review = [self.needs_review] if self.needs_review else []

        for doc in merge_docs:
            # add merge id to old pgpid list
            self.old_pgpids.append(doc.id)
            # add any tags from merge document tags to primary doc
            self.tags.add(*doc.tags.names())
            # add description if set and not duplicated
            if doc.description and doc.description not in self.description:
                description_chunks.append(
                    "Description from PGPID %s:\n%s" % (doc.id, doc.description)
                )
            # add any notes
            if doc.notes:
                notes.append("Notes from PGPID %s:\n%s" % (doc.id, doc.notes))
            if doc.needs_review:
                needs_review.append(doc.needs_review)

            # add languages and secondary languages
            for lang in doc.languages.all():
                self.languages.add(lang)
            for lang in doc.secondary_languages.all():
                self.secondary_languages.add(lang)
            if doc.language_note:
                language_notes.append(doc.language_note)

            # if there are any textblocks with fragments not already
            # asociated with this document, reassociate
            # (i.e., for newly discovered joins)
            # does not deal with discrepancies between text block fields or order
            for textblock in doc.textblock_set.all():
                if textblock.fragment not in self.fragments.all():
                    self.textblock_set.add(textblock)

            self._merge_footnotes(doc)
            self._merge_logentries(doc)

        # combine text fields
        self.description = "\n".join(description_chunks)
        self.notes = "\n".join(notes)
        self.language_note = "; ".join(language_notes)
        # if merged via script, flag for review
        if script:
            needs_review.insert(0, "SCRIPTMERGE")
        self.needs_review = "\n".join(needs_review)

        # save current document with changes; delete merged documents
        self.save()
        merged_ids = ", ".join([str(doc.id) for doc in merge_docs])
        for doc in merge_docs:
            doc.delete()
        # create log entry documenting the merge; include rationale
        doc_contenttype = ContentType.objects.get_for_model(Document)
        LogEntry.objects.log_action(
            user_id=user.id,
            content_type_id=doc_contenttype.pk,
            object_id=self.pk,
            object_repr=str(self),
            change_message="merged with %s: %s" % (merged_ids, rationale),
            action_flag=CHANGE,
        )

    def _merge_footnotes(self, doc):
        # combine footnotes; footnote logic for merge_with
        for footnote in doc.footnotes.all():
            # first, check for an exact match
            equiv_fn = self.footnotes.includes_footnote(footnote)
            # if there is no exact match, check again ignoring content
            if not equiv_fn:
                equiv_fn = self.footnotes.includes_footnote(
                    footnote, include_content=False
                )
                # if there's a partial match (everything but content)
                if equiv_fn:
                    # if the new footnote has content, add it
                    if footnote.content:
                        self.footnotes.add(footnote)
                    # if the partial match has no content, remove it
                    # (if it has any content, then it is different from the new one
                    # and should be preserved)
                    if not equiv_fn.content:
                        self.footnotes.remove(equiv_fn)

                # if neither an exact or partial match, add the new footnote
                else:
                    self.footnotes.add(footnote)

    def _merge_logentries(self, doc):
        # reassociate log entries; logic for merge_with
        # make a list of currently associated log entries to skip duplicates
        current_logs = [
            "%s_%s" % (le.user_id, le.action_time.isoformat())
            for le in self.log_entries.all()
        ]
        for log_entry in doc.log_entries.all():
            # check duplicate log entries, based on user id and time
            # (likely only applies to historic input & revision)
            if (
                "%s_%s" % (log_entry.user_id, log_entry.action_time.isoformat())
                in current_logs
            ):
                # skip if it's a duplicate
                continue

            # otherwise annotate and reassociate
            # - modify change message to document which object this event applied to
            log_entry.change_message = "%s [PGPID %d]" % (
                log_entry.change_message,
                doc.pk,
            )

            # - associate with the primary document
            log_entry.object_id = self.id
            log_entry.content_type_id = ContentType.objects.get_for_model(Document)
            log_entry.save()
예제 #20
0
파일: models.py 프로젝트: Khoding/khoBlog
class Page(auto_prefetch.Model):
    title = models.CharField(_("title"), max_length=200)
    content = models.TextField(_("content"), blank=True)
    description = models.TextField(blank=True, default="")
    page_head = models.TextField("Page head", blank=True)
    slug = models.SlugField(unique=True, default="", max_length=200)
    created_date = models.DateTimeField("Creation date", default=timezone.now)
    mod_date = models.DateTimeField("Last Updated", auto_now=True)
    main_page = models.BooleanField(default=False)
    enable_comments = models.BooleanField(_("enable comments"), default=True)
    withdrawn = models.BooleanField(default=False)
    template_name = models.CharField(
        _("template name"),
        max_length=70,
        blank=True,
        help_text=_(
            "Example: pages/contact_page.html”. If this isn’t provided, "
            "the system will use pages/default.html”."),
    )
    registration_required = models.BooleanField(
        _("registration required"),
        help_text=
        _("If this is checked, only logged-in users will be able to view the page."
          ),
        default=False,
    )
    sites = models.ManyToManyField(Site, verbose_name=_("sites"))
    tags = TaggableManager(blank=True, through=CustomTaggedItem)
    is_removed = models.BooleanField("is removed",
                                     default=False,
                                     db_index=True,
                                     help_text=("Soft delete"))
    history = HistoricalRecords()

    class Meta:
        """Meta class for Page Model"""

        ordering = ["pk"]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        return super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("pages:page_detail", kwargs={"slug": self.slug})

    def get_absolute_update_url(self):
        return reverse("pages:page_edit", kwargs={"slug": self.slug})

    def get_absolute_delete_url(self):
        return reverse("pages:page_delete", kwargs={"slug": self.slug})

    def get_absolute_admin_update_url(self):
        return reverse("admin:pages_page_change",
                       kwargs={"object_id": self.pk})

    def remove(self):
        self.is_removed = True
        self.save()

    def get_index_view_url(self):
        content_type = ContentType.objects.get_for_model(self.__class__)
        return reverse("%s:index" % (content_type.app_label))
예제 #21
0
class TagsMixin(models.Model):
    """
    Mixin for adding tags to a model.
    """
    # Make association with tags optional.
    if TaggableManager is not None:
        tags = TaggableManager(blank=True)
    else:
        tags = None

    class Meta:
        abstract = True


    def similar_objects(self, num=None, **filters):
        """
        Find similar objects using related tags.
        """
        tags = self.tags
        if not tags:
            return []

        content_type = ContentType.objects.get_for_model(self.__class__)
        filters['content_type'] = content_type

        # can't filter, see
        # - https://github.com/alex/django-taggit/issues/32
        # - https://django-taggit.readthedocs.io/en/latest/api.html#TaggableManager.similar_objects
        #
        # Otherwise this would be possible:
        # return tags.similar_objects(**filters)

        lookup_kwargs = tags._lookup_kwargs()
        lookup_keys = sorted(lookup_kwargs)
        qs = tags.through.objects.values(*lookup_kwargs.keys())
        qs = qs.annotate(n=models.Count('pk'))
        qs = qs.exclude(**lookup_kwargs)
        subq = tags.all()
        qs = qs.filter(tag__in=list(subq))
        qs = qs.order_by('-n')

        # from https://github.com/alex/django-taggit/issues/32#issuecomment-1002491
        if filters is not None:
            qs = qs.filter(**filters)

        if num is not None:
            qs = qs[:num]

        # Normal taggit code continues

        # TODO: This all feels like a bit of a hack.
        items = {}
        if len(lookup_keys) == 1:
            # Can we do this without a second query by using a select_related()
            # somehow?
            f = tags.through._meta.get_field_by_name(lookup_keys[0])[0]
            objs = f.rel.to._default_manager.filter(**{
                "%s__in" % f.rel.field_name: [r["content_object"] for r in qs]
            })
            for obj in objs:
                items[(getattr(obj, f.rel.field_name),)] = obj
        else:
            preload = {}
            for result in qs:
                preload.setdefault(result['content_type'], set())
                preload[result["content_type"]].add(result["object_id"])

            for ct, obj_ids in preload.items():
                ct = ContentType.objects.get_for_id(ct)
                for obj in ct.model_class()._default_manager.filter(pk__in=obj_ids):
                    items[(ct.pk, obj.pk)] = obj

        results = []
        for result in qs:
            obj = items[
                tuple(result[k] for k in lookup_keys)
            ]
            obj.similar_tags = result["n"]
            results.append(obj)
        return results
예제 #22
0
class Post(AbstractEntry):
    # Todo: 统一 Post 和 Material 的状态选项
    STATUS_CHOICES = Choices(
        (1, "published", _("published")),
        (2, "draft", _("draft")),
        (3, "hidden", _("hidden")),
    )

    status = models.PositiveSmallIntegerField(_("status"),
                                              choices=STATUS_CHOICES,
                                              default=STATUS_CHOICES.draft)
    pinned = models.BooleanField(_("pinned"), default=False)

    # Todo:移除封面字段
    cover = models.ImageField(_("cover"),
                              upload_to="covers/posts/",
                              blank=True)
    cover_thumbnail = ImageSpecField(
        source="cover",
        processors=[ResizeToFill(60, 60)],
        format="JPEG",
        options={"quality": 90},
    )

    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        verbose_name=_("category"),
        null=True,
        blank=True,
    )
    tags = TaggableManager(verbose_name=_("tags"),
                           through=TaggedItem,
                           blank=True)

    # 模型管理器
    objects = PostManager()
    index = IndexPostManager()

    class Meta:
        verbose_name = _("Posts")
        verbose_name_plural = _("Posts")
        ordering = ["-pub_date", "-created"]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.excerpt:
            self.excerpt = strip_tags(self.body_html)[:150]

        if not self.pub_date and self.status == self.STATUS_CHOICES.published:
            self.pub_date = self.created

        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("blog:detail", kwargs={"pk": self.pk})

    @property
    def type(self):
        return "p"