1from logging import getLogger
2from random import SystemRandom
3
4from django.apps import apps
5from django.db import models
6from django.urls import reverse
7from django.utils.translation import ugettext_lazy as _
8from six import python_2_unicode_compatible
9
10from dpaste import highlight
11
12config = apps.get_app_config('dpaste')
13logger = getLogger(__file__)
14R = SystemRandom()
15
16
17def generate_secret_id(length):
18    if length > config.SLUG_LENGTH:
19        logger.warning(
20            'Slug creation triggered a duplicate, '
21            'consider increasing the SLUG_LENGTH.'
22        )
23
24    secret_id = ''.join(
25        [
26            R.choice(config.SLUG_CHOICES)
27            for i in range(length or config.SLUG_LENGTH)
28        ]
29    )
30
31    # Check if this slug already exists, if not, return this new slug
32    try:
33        Snippet.objects.get(secret_id=secret_id)
34    except Snippet.DoesNotExist:
35        return secret_id
36
37    # Otherwise create a new slug which is +1 character longer
38    # than the previous one.
39    return generate_secret_id(length=length + 1)
40
41
42@python_2_unicode_compatible
43class Snippet(models.Model):
44    EXPIRE_TIME = 1
45    EXPIRE_KEEP = 2
46    EXPIRE_ONETIME = 3
47    EXPIRE_CHOICES = (
48        (EXPIRE_TIME, _('Expire by timestamp')),
49        (EXPIRE_KEEP, _('Keep Forever')),
50        (EXPIRE_ONETIME, _('One-Time snippet')),
51    )
52
53    secret_id = models.CharField(
54        _('Secret ID'), max_length=255, blank=True, null=True, unique=True
55    )
56    content = models.TextField(_('Content'))
57    lexer = models.CharField(
58        _('Lexer'), max_length=30, default=highlight.LEXER_DEFAULT
59    )
60    published = models.DateTimeField(_('Published'), auto_now_add=True)
61    expire_type = models.PositiveSmallIntegerField(
62        _('Expire Type'), choices=EXPIRE_CHOICES, default=EXPIRE_CHOICES[0][0]
63    )
64    expires = models.DateTimeField(_('Expires'), blank=True, null=True)
65    view_count = models.PositiveIntegerField(_('View count'), default=0)
66    rtl = models.BooleanField(_('Right-to-left'), default=False)
67    parent = models.ForeignKey(
68        'self',
69        null=True,
70        blank=True,
71        verbose_name=_('Parent Snippet'),
72        related_name='children',
73        on_delete=models.CASCADE,
74    )
75
76    class Meta:
77        ordering = ('-published',)
78        db_table = 'dpaste_snippet'
79
80    def __str__(self):
81        return self.secret_id
82
83    def save(self, *args, **kwargs):
84        if not self.secret_id:
85            self.secret_id = generate_secret_id(length=config.SLUG_LENGTH)
86        super(Snippet, self).save(*args, **kwargs)
87
88    def get_absolute_url(self):
89        return reverse('snippet_details', kwargs={'snippet_id': self.secret_id})
90
91    def highlight(self):
92        HighlighterClass = highlight.get_highlighter_class(self.lexer)
93        return HighlighterClass().render(
94            code_string=self.content,
95            lexer_name=self.lexer,
96            direction='rtl' if self.rtl else 'ltr',
97        )
98
99    @property
100    def lexer_name(self):
101        """Display name for this lexer."""
102        return highlight.Highlighter.get_lexer_display_name(self.lexer)
103
104    @property
105    def remaining_views(self):
106        if self.expire_type == self.EXPIRE_ONETIME:
107            remaining = config.ONETIME_LIMIT - self.view_count
108            return remaining > 0 and remaining or 0
109