11 | {% if reason == "email_send_failed" %}
12 | Verification message couldn't be sent
13 | The verification message to verify your email address couldn't be sent.
14 | {% endif %}
15 |
16 |
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/highlighting/get_languages.py:
--------------------------------------------------------------------------------
1 | """
2 | Iterate through all languages supported by Pygments and print a sorted list of languages
3 |
4 | This can then be copied into settings.py
5 | """
6 | from pygments.lexers import get_all_lexers
7 | import operator
8 |
9 | languages = {}
10 |
11 | for lexer in get_all_lexers():
12 | if lexer[1][0] != "text":
13 | languages[lexer[1][0]] = lexer[0]
14 |
15 | sorted_list = sorted(languages.items(), key=operator.itemgetter(0))
16 |
17 | # Add "text only" at the beginning
18 | sorted_list.insert(0, ("text", "Text only"))
19 |
20 | print(sorted_list)
--------------------------------------------------------------------------------
/home/jinja2/home/home.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
8 |
Upload a new paste
9 |
10 | {% include "home/submit_paste_form.html" %}
11 |
9 | {% if reason == "incorrect_user" %}
10 | Incorrect user
11 | You tried to change the settings of another user. That's not nice.
12 | {% elif reason == "not_logged_in" %}
13 | Not logged in
14 | You need to be logged in in order to change your settings.
15 | {% endif %}
16 |
9 | {% if reason == "incorrect_user" %}
10 | Incorrect user
11 | You tried to change the settings of another user. That's not nice.
12 | {% elif reason == "not_logged_in" %}
13 | Not logged in
14 | You need to be logged in in order to change your settings.
15 | {% endif %}
16 |
19 | {% endblock content %}
--------------------------------------------------------------------------------
/pastebin/testcase.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | from django.core.cache import cache
4 |
5 | from django_redis import get_redis_connection
6 |
7 | class CacheAwareTestCase(TestCase):
8 | """
9 | Cache-aware TestCase that clears the Redis storage and cache on startup
10 | """
11 | def clearCache(self):
12 | """
13 | Clears the cache
14 |
15 | Can be invoked manually if the unit test requires it
16 | """
17 | cache.clear()
18 | con = get_redis_connection("persistent")
19 |
20 | con.flushall()
21 |
22 | def setUp(self):
23 | super(CacheAwareTestCase, self).setUp()
24 |
25 | self.clearCache()
--------------------------------------------------------------------------------
/highlighting/formatter.py:
--------------------------------------------------------------------------------
1 | from pygments.formatters import HtmlFormatter
2 |
3 | class ListHtmlFormatter(HtmlFormatter):
4 | """
5 | Wraps the code inside an element and each individual line around a
element,
6 | giving us both free line numbers and better behavior with line wrapping.
7 |
8 | This imitates how GeSHi renders its highlighted code.
9 | """
10 | def wrap(self, source, outfile):
11 | return self._wrap_ol(source)
12 |
13 | def _wrap_ol(self, source):
14 | yield 0, ''
15 | for i, t in source:
16 | if i == 1:
17 | t = '
11 | Registered!
12 | You have successfully registered. You may now login.
13 | {% if email_address_added %}
14 | In addition, a verification link was sent to your email address. You need to verify your email address in order to be able to reset your password in case you are unable to login.
15 | {% endif %}
16 | Login
17 |
11 | {% if reason == "not_found" %}
12 | Paste not found
13 | The paste you tried to report was not found.
14 | {% elif reason == "removed" %}
15 | Paste removed
16 | This paste has been removed and can no longer be reported.
17 | {% elif reason == "expired" %}
18 | Paste expired
19 | This paste has expired and can no longer be reported.
20 | {% endif %}
21 |
11 | {% if reason == "not_found" %}
12 | Paste not found
13 | The paste you tried to report was not found.
14 | {% elif reason == "removed" %}
15 | Paste removed
16 | This paste has been removed and can no longer be reported.
17 | {% elif reason == "expired" %}
18 | Paste expired
19 | This paste has expired and can no longer be reported.
20 | {% endif %}
21 |
6 | You are about to delete your account!
7 | Deleting your account also removes any pastes you have uploaded. After deleted, your account can't be restored!
8 |
6 | You are about to delete your account!
7 | Deleting your account also removes any pastes you have uploaded. After deleted, your account can't be restored!
8 |
9 |
18 |
19 | {% endblock profile_content %}
--------------------------------------------------------------------------------
/users/migrations/0002_sitesettings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | from django.conf import settings
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ('users', '0001_initial'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='SiteSettings',
18 | fields=[
19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20 | ('public_favorites', models.BooleanField(default=True)),
21 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
22 | ],
23 | options={
24 | },
25 | bases=(models.Model,),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/sql/add_test_data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO pastes (char_id, title, hash, hidden, submitted) VALUES
2 | ('A5vSfcXu', 'Test paste', 'a2ee48bd00238c5a5f42d71cadf1d418d01827fda4acf90fd413debec5d2c5dc', false,
3 | timestamp '2015-03-01 12:00:00'),
4 | ('d5dAASXC', 'Another test paste', 'd4b1a4af2c8852b9df30097e075ca17be6174b8e7f29437257474e3314a39f5d', false,
5 | timestamp '2015-03-01 12:00:00');
6 |
7 | INSERT INTO paste_content (hash, text, formatted_text) VALUES
8 | ('a2ee48bd00238c5a5f42d71cadf1d418d01827fda4acf90fd413debec5d2c5dc',
9 | 'This is a test paste.',
10 | 'This is a test paste.'),
11 | ('d4b1a4af2c8852b9df30097e075ca17be6174b8e7f29437257474e3314a39f5d',
12 | 'This is another test paste. Unfortunately, this one doesn''t have any interesting content either.',
13 | 'This is another test paste. Unfortunately, this one doesn''t have any interesting content either.');
--------------------------------------------------------------------------------
/users/templates/users/profile/profile.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}User: {{ profile_user.get_username }} - pastebin-django{% endblock %}
4 |
5 | {% block content %}
6 |
9 | {% if reason == "expired" %}
10 | Paste expired
11 | The paste you tried to view has expired and can no longer be viewed.
12 | {% elif reason == "user_removed" %}
13 | Paste removed
14 | The paste was removed by the uploader.
15 | {% elif reason == "admin_removed" %}
16 | Paste removed
17 | The paste was removed by an administrator.
18 | {% elif reason == "not_found" %}
19 | Paste not found
20 | The paste you tried to view does not exist.
21 | {% endif %}
22 | {% if removal_reason %}
23 | Reason for removal:
24 |
9 | {% if reason == "expired" %}
10 | Paste expired
11 | The paste you tried to view has expired and can no longer be viewed.
12 | {% elif reason == "user_removed" %}
13 | Paste removed
14 | The paste was removed by the uploader.
15 | {% elif reason == "admin_removed" %}
16 | Paste removed
17 | The paste was removed by an administrator.
18 | {% elif reason == "not_found" %}
19 | Paste not found
20 | The paste you tried to view does not exist.
21 | {% endif %}
22 | {% if removal_reason %}
23 | Reason for removal:
24 |
11 | {% if reason == "not_found" %}
12 | Paste not found
13 | The paste you tried to edit was not found.
14 | {% elif reason == "not_logged_in" %}
15 | Not logged in
16 | You have to be logged in to edit your own pastes.
17 | {% elif reason == "not_owner" %}
18 | Not permitted
19 | You can't edit a paste you didn't upload yourself.
20 | {% elif reason == "removed" %}
21 | Paste removed
22 | This paste has been removed and can no longer be edited.
23 | {% elif reason == "expired" %}
24 | Paste expired
25 | This paste has expired and can no longer be edited.
26 | {% endif %}
27 |
11 | {% if reason == "not_found" %}
12 | Paste not found
13 | The paste you tried to edit was not found.
14 | {% elif reason == "not_logged_in" %}
15 | Not logged in
16 | You have to be logged in to edit your own pastes.
17 | {% elif reason == "not_owner" %}
18 | Not permitted
19 | You can't edit a paste you didn't upload yourself.
20 | {% elif reason == "removed" %}
21 | Paste removed
22 | This paste has been removed and can no longer be edited.
23 | {% elif reason == "expired" %}
24 | Paste expired
25 | This paste has expired and can no longer be edited.
26 | {% endif %}
27 |
28 |
29 |
30 |
31 | {% endblock %}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pastebin-django
2 | ===
3 | A pastebin web application developed in Django featuring plenty of features, including:
4 |
5 | * User registration, allowing pastes to be updated and removed
6 | * Syntax highlighting (server-side Pygments used for unencrypted pastes, client-side Prism JS library for encrypted pastes)
7 | * Paste encryption using Stanford Javascript Crypto Library
8 | * Hidden pastes
9 | * Paste versioning and history
10 | * Feature to report pastes to site administrators
11 | * Paste favoriting
12 | * Paste comments
13 |
14 | Looking for the old version of pastebin-django as it was developed for Tsoha2015? [Check it out here.](https://github.com/Matoking/pastebin-django/tree/tsoha)
15 |
16 | Credits
17 | --
18 | pastebin-django is built on the Django web framework
19 |
20 | [Django](https://www.djangoproject.com/)
21 |
22 | and it uses the following JavaScript libraries
23 |
24 | [Stanford Javascript Crypto Library](https://github.com/bitwiseshiftleft/sjcl)
25 | [Prism](http://prismjs.com/)
26 | [JQuery](http://jquery.com/)
27 | [Timeago](http://timeago.yarp.com/)
28 | [Readmore.js](http://jedfoster.com/Readmore.js/)
29 |
--------------------------------------------------------------------------------
/pastes/jinja2/pastes/remove_paste/remove_error.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
8 |
Remove paste
9 |
10 |
11 | {% if reason == "not_found" %}
12 | Paste not found
13 | The paste you tried to remove was not found.
14 | {% elif reason == "not_logged_in" %}
15 | Not logged in
16 | You have to be logged in to remove your own pastes.
17 | {% elif reason == "not_owner" %}
18 | Not permitted
19 | You can't remove a paste you didn't upload yourself. It would be really bad if you could.
20 | {% elif reason == "removed" %}
21 | Paste removed
22 | This paste has been removed and can no longer be removed.
23 | {% elif reason == "expired" %}
24 | Paste expired
25 | This paste has expired and can no longer be removed.
26 | {% endif %}
27 |
11 | {% if reason == "not_found" %}
12 | Paste not found
13 | The paste you tried to remove was not found.
14 | {% elif reason == "not_logged_in" %}
15 | Not logged in
16 | You have to be logged in to remove your own pastes.
17 | {% elif reason == "not_owner" %}
18 | Not permitted
19 | You can't remove a paste you didn't upload yourself. It would be really bad if you could.
20 | {% elif reason == "removed" %}
21 | Paste removed
22 | This paste has been removed and can no longer be removed.
23 | {% elif reason == "expired" %}
24 | Paste expired
25 | This paste has expired and can no longer be removed.
26 | {% endif %}
27 |
13 | You are removing the paste {{ paste.title }}.
In addition, someone who found your paste useful and wanted to share it with someone might be extremely pissed off to find out you had removed it.
14 |
15 |
25 |
26 |
27 |
28 | {% endblock %}
--------------------------------------------------------------------------------
/users/jinja2/users/profile/home/home_favorites.html:
--------------------------------------------------------------------------------
1 | {% if not profile_settings.public_favorites and request.user != profile_user %}
2 |
3 | Private favorites
4 | This user has opted to make his or her favorites private.
5 |
13 | You are removing the paste {{ paste.title }}.
In addition, someone who found your paste useful and wanted to share it with someone might be extremely pissed off to find out you had removed it.
14 |
15 |
25 |
26 |
27 |
28 | {% endblock %}
--------------------------------------------------------------------------------
/pastebin/manager.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.core.cache import cache
3 |
4 | class CachedManager(models.Manager):
5 | """
6 | A custom manager that implements simple caching that is used when fetching single entries
7 | """
8 | # Key that will be used for caching
9 | # Eg. with ["char_id", "id"] the model will be saved and retrieved from cache but only
10 | # if the get() method is given a single char_id OR id argument
11 | cache_keys = []
12 | cache_name = None
13 |
14 | def get(self, *args, **kwargs):
15 | """
16 | If only one argument is passed and it's in cache_keys, the result
17 | will be attempted to be pulled from cache
18 | """
19 | if len(kwargs) == 1 and kwargs[kwargs.keys()[0]] in self.cache_keys:
20 | cache_key = kwargs[kwargs.keys()[0]]
21 | result = cache.get("%s:%s" % (self.cache_name, cached_key))
22 |
23 | if result != None:
24 | return result
25 |
26 | result = super(CachedManager, self).get(*args, **kwargs)
27 |
28 | for cache_key in cache_keys:
29 | cache.set("%s:%s" % (self.cache_name, cache_key), result)
30 |
31 | return result
--------------------------------------------------------------------------------
/pastes/migrations/0010_auto_20150731_1746.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('pastes', '0009_auto_20150731_1327'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='paste',
16 | name='submitted',
17 | field=models.DateTimeField(auto_now_add=True, db_index=True),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='paste',
22 | name='updated',
23 | field=models.DateTimeField(auto_now=True, db_index=True),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='pastereport',
28 | name='checked',
29 | field=models.BooleanField(default=False, db_index=True),
30 | preserve_default=True,
31 | ),
32 | migrations.AlterField(
33 | model_name='pasteversion',
34 | name='submitted',
35 | field=models.DateTimeField(auto_now_add=True, db_index=True),
36 | preserve_default=True,
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/pastes/templates/pastes/paste_history/paste_history.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load humanize %}
3 |
4 | {% block content %}
5 |
28 | {% endif %}
29 | {% endif %}
30 | {% endfor %}
--------------------------------------------------------------------------------
/highlighting/__init__.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from itertools import chain
3 |
4 | from highlighting import settings
5 | from highlighting.formatter import ListHtmlFormatter
6 |
7 | from pygments import highlight
8 | from pygments.lexers import get_lexer_by_name
9 | from pygments.formatters import HtmlFormatter
10 |
11 | def language_exists(language):
12 | """
13 | Check that the language exists in the tuple
14 | """
15 | if language in chain(*settings.LANGUAGES):
16 | return True
17 | else:
18 | return False
19 |
20 | def format_text(text, format="text"):
21 | """
22 | Format the text using Pygments and return the formatted text
23 | """
24 | lexer = get_lexer_by_name(format)
25 | formatter = ListHtmlFormatter(linenos=False,
26 | prestyles="border-radius: 0px; background-color: white; border: 0px;")
27 | result = highlight(text, lexer, formatter)
28 |
29 | # A small hack to include CSS styles in the table containing the line numbers
30 | # This needs to be done because otherwise Bootstrap inserts its own styling to the pre-element,
31 | # which makes it look like crap
32 | result = result.replace("
37 | {% endblock %}
38 | {% block extra_js %}
39 | {% load staticfiles %}
40 |
41 |
42 | {% endblock %}
--------------------------------------------------------------------------------
/templates_jinja2/form.html:
--------------------------------------------------------------------------------
1 | {# A template that allows certain Bootstrap forms to be implemented more easily #}
2 | {% for field in form %}
3 | {% if not exclude or field.name not in exclude %}
4 | {% if field.field.widget.input_type == "hidden" %}
5 |
6 | {% else %}
7 | {% if field.errors %}
8 |
13 | What is pastebin-django?
14 | pastebin-django is a site that allows you to store 'pastes', short text snippets.
15 |
16 |
17 | What is the syntax highlighting feature?
18 | If your paste contains code or markup, you can use the syntax highlighting to highlight the text. Note that encrypted pastes don't support all of the syntax highlighting formats that are available with unencrypted pastes.
19 |
20 |
21 | How does the encryption feature work?
22 | Your pastes can be encrypted to ensure only people you trust will be able to read them. Encrypting your paste means you need to provide the same password you used to encrypt paste to decrypt and thus read it. Since the encryption and decryption are done locally on your computer, the administrators of this site won't be able to recover the content of your encrypted paste if you lose your password.
23 |
24 |
25 | How does the paste history feature work?
26 | If you have uploaded your paste while logged in, you can update your paste later on. Paste history means the earlier versions of your paste remain available.
27 |
28 |
29 | Can I comment on pastes?
30 | Yes, if you have registered you can comment on pastes.
31 |
32 |
33 | What software does this site run on?
34 | This pastebin site runs on pastebin-django, a web application developed using the Django web framework.
35 |
13 | What is pastebin-django?
14 | pastebin-django is a site that allows you to store 'pastes', short text snippets.
15 |
16 |
17 | What is the syntax highlighting feature?
18 | If your paste contains code or markup, you can use the syntax highlighting to highlight the text. Note that encrypted pastes don't support all of the syntax highlighting formats that are available with unencrypted pastes.
19 |
20 |
21 | How does the encryption feature work?
22 | Your pastes can be encrypted to ensure only people you trust will be able to read them. Encrypting your paste means you need to provide the same password you used to encrypt paste to decrypt and thus read it. Since the encryption and decryption are done locally on your computer, the administrators of this site won't be able to recover the content of your encrypted paste if you lose your password.
23 |
24 |
25 | How does the paste history feature work?
26 | If you have uploaded your paste while logged in, you can update your paste later on. Paste history means the earlier versions of your paste remain available.
27 |
28 |
29 | Can I comment on pastes?
30 | Yes, if you have registered you can comment on pastes.
31 |
32 |
33 | What software does this site run on?
34 | This pastebin site runs on pastebin-django, a web application developed using the Django web framework.
35 |
36 |
37 |
38 |
39 | {% endblock content %}
--------------------------------------------------------------------------------
/sql/create_tables.sql:
--------------------------------------------------------------------------------
1 | -- table of all the pastes
2 | CREATE TABLE pastes (
3 | id SERIAL PRIMARY KEY,
4 | char_id CHAR(8) NOT NULL, -- 8-character identifier, used in the URL
5 | user_id INTEGER DEFAULT NULL REFERENCES auth_user(id), -- reference to registered user as defined by Django, can be NULL
6 |
7 | title VARCHAR(128) NOT NULL, -- Title of the paste
8 | format VARCHAR(32) NOT NULL, -- Formatting of the text (eg. "text" for plain text, "python" for Python code)
9 | hash CHAR(64) NOT NULL, -- Hash of the paste's text (SHA256, probably a bit overkill),
10 | -- which is then used as the identifier to the actual paste content
11 | -- This means duplicate pastes don't waste space
12 |
13 | expiration_date TIMESTAMP DEFAULT NULL, -- Date after which the paste is considered expired
14 | -- NULL if paste doesn't have an expiration date
15 | hidden BOOLEAN NOT NULL,
16 |
17 | submitted TIMESTAMP NOT NULL -- Date when the paste was submitted
18 | );
19 |
20 | CREATE INDEX paste_id_index ON pastes(id);
21 | CREATE INDEX char_id_index ON pastes(char_id);
22 |
23 | -- Paste content is retrieved by a hash (SHA-256/MD5)
24 | -- meaning no space is wasted by duplicate pastes
25 | CREATE TABLE paste_content (
26 | id SERIAL PRIMARY KEY,
27 | hash CHAR(64) NOT NULL,
28 | format VARCHAR(32) NOT NULL, -- Formatting of the text. If "none", the text isn't formatted and is stored in its original form
29 | text TEXT NOT NULL
30 | );
31 |
32 | CREATE INDEX paste_content_id_index ON paste_content(id);
33 | CREATE INDEX paste_content_hash_index ON paste_content(hash);
34 |
35 | CREATE TABLE comments (
36 | id SERIAL PRIMARY KEY,
37 | paste_id INTEGER REFERENCES pastes(id),
38 | user_id INTEGER REFERENCES auth_user(id),
39 |
40 | text TEXT NOT NULL, -- the actual comment
41 |
42 | submitted TIMESTAMP NOT NULL,
43 | edited TIMESTAMP DEFAULT NULL
44 | );
45 |
46 | CREATE INDEX comment_id_index ON comments(id);
47 | CREATE INDEX comment_paste_id_index ON comments(paste_id);
48 |
49 | CREATE TABLE favorites (
50 | id SERIAL PRIMARY KEY,
51 | paste_id INTEGER REFERENCES pastes(id),
52 | user_id INTEGER REFERENCES auth_user(id),
53 | added TIMESTAMP NOT NULL
54 | );
--------------------------------------------------------------------------------
/users/templates/users/profile/profile_navbar.html:
--------------------------------------------------------------------------------
1 | {% load lineage %}
2 |
--------------------------------------------------------------------------------
/pastebin/util.py:
--------------------------------------------------------------------------------
1 | import math
2 | import datetime
3 |
4 | class Paginator(object):
5 | @staticmethod
6 | def get_pages(current_page, entries_per_page, total_entries):
7 | entries = []
8 |
9 | total_pages = math.ceil(float(total_entries) / float(entries_per_page))
10 |
11 | # Add the first and previous pages
12 | if current_page != 1:
13 | entries.append("first")
14 | entries.append("previous")
15 |
16 | # Add four pages before the current page
17 | for i in reversed(range(0, 4)):
18 | page = current_page - i - 1
19 |
20 | if page >= 1:
21 | entries.append(page)
22 |
23 | # Add the current page
24 | entries.append(current_page)
25 |
26 | # Add four pages after the current page
27 | for i in range(0, 4):
28 | page = current_page + i + 1
29 |
30 | if page <= total_pages:
31 | entries.append(page)
32 |
33 | # Add the next and last page
34 | if current_page != total_pages:
35 | entries.append("next")
36 | entries.append("last")
37 |
38 | return entries
39 |
40 | def queryset_to_list(queryset, fields=[], datetime_to_unix=True):
41 | """
42 | Converts a given Queryset into a list of dicts
43 |
44 | fields takes a list of model fields to retrieve. In addition, field lookups can be used
45 | and an equals sign can be used to separate the field lookup and the name to be used for the field
46 | in the result
47 | (eg. user__username=username)
48 |
49 | If datetime_to_unix is True, convert datetime objects to Unix timestamps
50 | """
51 | if queryset.count() == 0:
52 | return []
53 |
54 | # A dict of fields
55 | # Key describes the field lookup to use,
56 | # value describes the key to use for the retrieved value
57 | # Eg. user__username=username will have the key 'username' in the dict
58 | field_names = {}
59 |
60 | for field_name in fields:
61 | if not "=" in field_name:
62 | field_names[field_name] = field_name
63 | else:
64 | field_names[field_name[:field_name.find("=")]] = field_name[field_name.find("=")+1:]
65 |
66 | rows = list(queryset.values(*field_names.keys()))
67 |
68 | for i, row in enumerate(rows):
69 | for row_key, row_entry in row.iteritems():
70 | field_name = field_names[row_key]
71 |
72 | # Rename the field if we want a different name
73 | if row_key != field_name:
74 | value = row[row_key]
75 | del rows[i][row_key]
76 | rows[i][field_name] = value
77 |
78 | if datetime_to_unix:
79 | if type(rows[i][field_name]) is datetime.datetime:
80 | rows[i][field_name] = int(rows[i][field_name].strftime("%s"))
81 |
82 | return rows
--------------------------------------------------------------------------------
/static/js/pastebin-decrypt.js:
--------------------------------------------------------------------------------
1 | if (typeof pastebin === 'undefined') {
2 | var pastebin = {};
3 | pastebin.urls = {};
4 | }
5 |
6 | // List of languages prism.js can highlight
7 | // The key is the format used by pastebin-django,
8 | // the value is the format used in language-xxxx class by prism.js
9 | pastebin.languages = {
10 | "text": "markup",
11 | "c": "c",
12 | "css": "css",
13 | "js": "javascript",
14 | "as": "actionscript",
15 | "apacheconf": "apacheconf",
16 | "aspx-cs": "aspnet",
17 | "ahk": "autohotkey",
18 | "bash": "bash",
19 | "csharp": "csharp",
20 | "cpp": "cpp",
21 | "coffee-script": "coffeescript",
22 | "dart": "dart",
23 | "eiffel": "eiffel",
24 | "erlang": "erlang",
25 | "fsharp": "fsharp",
26 | "fortran": "fortran",
27 | "cucumber": "gherkin",
28 | "go": "go",
29 | "groovy": "groovy",
30 | "haml": "haml",
31 | "handlebars": "handlebars",
32 | "haskell": "haskell",
33 | "http": "http",
34 | "ini": "ini",
35 | "jade": "jade",
36 | "java": "java",
37 | "julia": "julia",
38 | "matlab": "matlab",
39 | "nasm": "nasm",
40 | "nsis": "nsis",
41 | "objective-c": "objectivec",
42 | "perl": "perl",
43 | "php": "php",
44 | "powershell": "powershell",
45 | "python": "python",
46 | "rst": "rest",
47 | "rb": "ruby",
48 | "rust": "rust",
49 | "sass": "scss",
50 | "scala": "scala",
51 | "scheme": "scheme",
52 | "smalltalk": "smalltalk",
53 | "smarty": "smarty",
54 | "sql": "sql",
55 | "swift": "swift",
56 | "twig": "twig",
57 | "ts": "typescript",
58 | "yaml": "yaml"
59 | };
60 |
61 | pastebin.loadDecrypt = function() {
62 | $("#paste-decrypt").click(function() { pastebin.decryptPaste(); });
63 |
64 | $("#paste-password").keyup(function(evt) {
65 | if (evt.keyCode == 13) {
66 | $("#paste-decrypt").click().focus();
67 | }
68 | });
69 | };
70 |
71 | pastebin.decryptPaste = function() {
72 | var password = $("#paste-password").val();
73 | var text = $("#encrypted-text").text();
74 |
75 | try {
76 | text = sjcl.decrypt(password, text);
77 | } catch (err) {
78 | // Paste couldn't be decrypted
79 | window.alert("Paste couldn't be decrypted. Your password is probably incorrect.");
80 | $("#paste-decrypt-password").select();
81 | return;
82 | }
83 |
84 | $("#encrypted-text").find("code").text(text);
85 | pastebin.highlightPaste();
86 | }
87 |
88 | /**
89 | * Highlight the now decrypted paste using prism.js
90 | */
91 | pastebin.highlightPaste = function() {
92 | // Check if prism.js supports the format of this paste
93 | // If it does, highlight it using that language
94 | // Otherwise, default to plain text
95 | var language = "markup";
96 |
97 | if (pastebin_paste_format in pastebin.languages) {
98 | language = pastebin.languages[pastebin_paste_format];
99 | }
100 |
101 | $("#encrypted-text").addClass("language-" + language + " line-numbers");
102 | $("#encrypted-text").show();
103 | $("#paste-decrypt-form").hide();
104 |
105 | Prism.highlightAll();
106 |
107 | $("#encrypted-text").addClass("pre-code");
108 |
109 | // Also show paste controls
110 | pastebin.showControls();
111 | };
112 |
113 | pastebin.loadDecrypt();
--------------------------------------------------------------------------------
/static/js/pastebin-favorite.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This script is responsible for updating the favorite button and displaying the relevant information.
3 | * This way the user can add/remove the paste from his favorites without having to refresh the page.
4 | */
5 | if (typeof pastebin === 'undefined') {
6 | var pastebin = {};
7 | pastebin.urls = {};
8 | }
9 |
10 | // An object containing different URLs for easier access
11 | pastebin.urls["change_paste_favorite"] = window.location.protocol + "//" + window.location.host + "/pastes/change_paste_favorite/";
12 |
13 | pastebin.loadFavorites = function() {
14 | // We need to send a CSRF token with POST requests, so make sure it's included
15 | // with every request
16 | $.ajaxSetup({
17 | beforeSend: function(xhr) {
18 | xhr.setRequestHeader('X-CSRFToken', pastebin_csrf_token);
19 | }
20 | });
21 |
22 | pastebin.updateFavoriteButton();
23 | };
24 |
25 | /**
26 | * Update the "add/remove favorite" button
27 | */
28 | pastebin.updateFavoriteButton = function() {
29 | // Hide the button if user isn't logged in, he's not supposed to see it anyway
30 | if (!pastebin_logged_in) {
31 | $("#favorite-button").css("display", "none");
32 | return;
33 | }
34 |
35 | if (pastebin_paste_favorited) {
36 | $("#favorite-button").html(" Remove from favorites");
37 | $("#favorite-button").attr("onclick", "pastebin.removeFromFavorites()");
38 | $("#favorite-button").attr("class", "btn btn-warning btn-xs");
39 | } else {
40 | $("#favorite-button").html(" Add to favorites");
41 | $("#favorite-button").attr("onclick", "pastebin.addToFavorites()");
42 | $("#favorite-button").attr("class", "btn btn-info btn-xs");
43 | }
44 | };
45 |
46 | /**
47 | * Called when user clicks "add to favorites"
48 | */
49 | pastebin.addToFavorites = function() {
50 | if (pastebin_paste_favorited) {
51 | return;
52 | }
53 |
54 | $.post(pastebin.urls["change_paste_favorite"],
55 | {char_id: pastebin_char_id,
56 | action: "add"},
57 | function(result) {
58 | pastebin.onFavoriteUpdated(result);
59 | });
60 |
61 | $("#favorite-button").attr("disabled", true);
62 | };
63 |
64 | /**
65 | * Called when user clicks "remove from favorites"
66 | */
67 | pastebin.removeFromFavorites = function() {
68 | if (!pastebin_paste_favorited) {
69 | return;
70 | }
71 |
72 | $.post(pastebin.urls["change_paste_favorite"],
73 | {char_id: pastebin_char_id,
74 | action: "remove"},
75 | function(result) {
76 | pastebin.onFavoriteUpdated(result);
77 | });
78 |
79 | $("#favorite-button").attr("disabled", true);
80 | };
81 |
82 | /**
83 | * Called when response is received to user's "add/remove favorite" request
84 | */
85 | pastebin.onFavoriteUpdated = function(result) {
86 | result = JSON.parse(result);
87 |
88 | if ("status" in result && result["status"] === "success") {
89 | if (result["data"]["favorited"]) {
90 | pastebin_paste_favorited = true;
91 | } else {
92 | pastebin_paste_favorited = false;
93 | }
94 | }
95 |
96 | $("#favorite-button").attr("disabled", false);
97 |
98 | pastebin.updateFavoriteButton();
99 | };
100 |
101 | pastebin.loadFavorites();
--------------------------------------------------------------------------------
/users/jinja2/users/settings/change_email_address/change_email_address.html:
--------------------------------------------------------------------------------
1 | {% extends "users/profile/profile.html" %}
2 |
3 | {% block profile_content %}
4 |
5 |
6 | If you have added an email address to your account and verified it, you can reset your password in case you find yourself unable to login to your account.
7 |
8 | {% if email_address_changed %}
9 |
10 |
11 | Email address changed!
12 | Your email address was changed and a verification link was sent to your new email address.
13 |
23 | Verification link resent!
24 | The verification link was resent to your email address. Note that the old verification link is no longer valid.
25 |
You are uploading this paste as user {{ user.get_username }}
13 | {% endif %}
14 |
15 |
16 | {% endif %}
17 | {# Print errors related to the text field here #}
18 | {% if form.text.errors %}
19 |
20 | {{ form.text.errors }}
21 |
22 | {% endif %}
23 | {% include "form.html" with form_label_col=2 form_field_col=8 exclude="text" %}
24 |
25 |
26 |
You can encrypt your paste to prevent people from viewing the content of your paste unless a correct password is provided. The encryption and decryption are performed client-side, meaning we never receive an unencrypted copy of the paste.
27 |
Note the following:
28 |
29 |
If you lose your password, we can't recover the content of your paste.
30 |
Syntax highlighting doesn't work with all languages if your paste is encrypted.
You are uploading this paste as user {{ request.user.get_username() }}
13 | {% endif %}
14 |
15 |
16 | {% endif %}
17 | {# Print errors related to the text field here #}
18 | {% if form.text.errors %}
19 |
20 | {{ form.text.errors }}
21 |
22 | {% endif %}
23 | {% with form_label_col=2, form_field_col=8, exclude="text,syntax_highlighting" %}{% include "form.html" %}{% endwith %}
24 | {% include "home/submit_paste_languages.html" %}
25 |
26 |
27 |
You can encrypt your paste to prevent people from viewing the content of your paste unless a correct password is provided. The encryption and decryption are performed client-side, meaning we never receive an unencrypted copy of the paste.
28 |
Note the following:
29 |
30 |
If you lose your password, we can't recover the content of your paste.
31 |
Syntax highlighting doesn't work with all languages if your paste is encrypted.
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
82 |
--------------------------------------------------------------------------------
/static/js/jquery.readmore.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @preserve
3 | *
4 | * Readmore.js jQuery plugin
5 | * Author: @jed_foster
6 | * Project home: http://jedfoster.github.io/Readmore.js
7 | * Licensed under the MIT license
8 | *
9 | * Debounce function from http://davidwalsh.name/javascript-debounce-function
10 | */
11 | !function(e){"use strict";function t(e,t,a){var i;return function(){var n=this,o=arguments,r=function(){i=null,a||e.apply(n,o)},s=a&&!i;clearTimeout(i),i=setTimeout(r,t),s&&e.apply(n,o)}}function a(e){var t=++h;return String(null==e?"rmjs-":e)+t}function i(e){var t=e.clone().css({height:"auto",width:e.width(),maxHeight:"none",overflow:"hidden"}).insertAfter(e),a=t.outerHeight(),i=parseInt(t.css({maxHeight:""}).css("max-height").replace(/[^-\d\.]/g,""),10),n=e.data("defaultHeight");t.remove();var o=i||e.data("collapsedHeight")||n;e.data({expandedHeight:a,maxHeight:i,collapsedHeight:o}).css({maxHeight:"none"})}function n(e){if(!d[e.selector]){var t=" ";e.embedCSS&&""!==e.blockCSS&&(t+=e.selector+" + [data-readmore-toggle], "+e.selector+"[data-readmore]{"+e.blockCSS+"}"),t+=e.selector+"[data-readmore]{transition: height "+e.speed+"ms;overflow: hidden;}",function(e,t){var a=e.createElement("style");a.type="text/css",a.styleSheet?a.styleSheet.cssText=t:a.appendChild(e.createTextNode(t)),e.getElementsByTagName("head")[0].appendChild(a)}(document,t),d[e.selector]=!0}}function o(t,a){this.element=t,this.options=e.extend({},s,a),n(this.options),this._defaults=s,this._name=r,this.init(),window.addEventListener?(window.addEventListener("load",l),window.addEventListener("resize",l)):(window.attachEvent("load",l),window.attachEvent("resize",l))}var r="readmore",s={speed:100,collapsedHeight:200,heightMargin:16,moreLink:'Read More',lessLink:'Close',embedCSS:!0,blockCSS:"display: block; width: 100%;",startOpen:!1,beforeToggle:function(){},afterToggle:function(){}},d={},h=0,l=t(function(){e("[data-readmore]").each(function(){var t=e(this),a="true"===t.attr("aria-expanded");i(t),t.css({height:t.data(a?"expandedHeight":"collapsedHeight")})})},100);o.prototype={init:function(){var t=this,n=e(this.element);n.data({defaultHeight:this.options.collapsedHeight,heightMargin:this.options.heightMargin}),i(n);var o=n.data("collapsedHeight"),r=n.data("heightMargin");if(n.outerHeight(!0)<=o+r)return!0;var s=n.attr("id")||a(),d=t.options.startOpen?t.options.lessLink:t.options.moreLink;n.attr({"data-readmore":"","aria-expanded":!1,id:s}),n.after(e(d).on("click",function(e){t.toggle(this,n[0],e)}).attr({"data-readmore-toggle":"","aria-controls":s})),t.options.startOpen||n.css({height:o})},toggle:function(t,a,i){i&&i.preventDefault(),t||(t=e('[aria-controls="'+this.element.id+'"]')[0]),a||(a=this.element);var n=this,o=e(a),r="",s="",d=!1,h=o.data("collapsedHeight");o.height()<=h?(r=o.data("expandedHeight")+"px",s="lessLink",d=!0):(r=h,s="moreLink"),n.options.beforeToggle(t,a,!d),o.css({height:r}),o.on("transitionend",function(){n.options.afterToggle(t,a,d),e(this).attr({"aria-expanded":d}).off("transitionend")}),e(t).replaceWith(e(n.options[s]).on("click",function(e){n.toggle(this,a,e)}).attr({"data-readmore-toggle":"","aria-controls":o.attr("id")}))},destroy:function(){e(this.element).each(function(){var t=e(this);t.attr({"data-readmore":null,"aria-expanded":null}).css({maxHeight:"",height:""}).next("[data-readmore-toggle]").remove(),t.removeData()})}},e.fn.readmore=function(t){var a=arguments,i=this.selector;return t=t||{},"object"==typeof t?this.each(function(){if(e.data(this,"plugin_"+r)){var a=e.data(this,"plugin_"+r);a.destroy.apply(a)}t.selector=i,e.data(this,"plugin_"+r,new o(this,t))}):"string"==typeof t&&"_"!==t[0]&&"init"!==t?this.each(function(){var i=e.data(this,"plugin_"+r);i instanceof o&&"function"==typeof i[t]&&i[t].apply(i,Array.prototype.slice.call(a,1))}):void 0}}(jQuery);
--------------------------------------------------------------------------------
/static/css/pygments-style.css:
--------------------------------------------------------------------------------
1 | .hll { background-color: #ffffcc }
2 | .c { color: #408080; font-style: italic } /* Comment */
3 | .err { border: 1px solid #FF0000 } /* Error */
4 | .k { color: #008000; font-weight: bold } /* Keyword */
5 | .o { color: #666666 } /* Operator */
6 | .cm { color: #408080; font-style: italic } /* Comment.Multiline */
7 | .cp { color: #BC7A00 } /* Comment.Preproc */
8 | .c1 { color: #408080; font-style: italic } /* Comment.Single */
9 | .cs { color: #408080; font-style: italic } /* Comment.Special */
10 | .gd { color: #A00000 } /* Generic.Deleted */
11 | .ge { font-style: italic } /* Generic.Emph */
12 | .gr { color: #FF0000 } /* Generic.Error */
13 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */
14 | .gi { color: #00A000 } /* Generic.Inserted */
15 | .go { color: #888888 } /* Generic.Output */
16 | .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
17 | .gs { font-weight: bold } /* Generic.Strong */
18 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
19 | .gt { color: #0044DD } /* Generic.Traceback */
20 | .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
21 | .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
22 | .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
23 | .kp { color: #008000 } /* Keyword.Pseudo */
24 | .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
25 | .kt { color: #B00040 } /* Keyword.Type */
26 | .m { color: #666666 } /* Literal.Number */
27 | .s { color: #BA2121 } /* Literal.String */
28 | .na { color: #7D9029 } /* Name.Attribute */
29 | .nb { color: #008000 } /* Name.Builtin */
30 | .nc { color: #0000FF; font-weight: bold } /* Name.Class */
31 | .no { color: #880000 } /* Name.Constant */
32 | .nd { color: #AA22FF } /* Name.Decorator */
33 | .ni { color: #999999; font-weight: bold } /* Name.Entity */
34 | .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
35 | .nf { color: #0000FF } /* Name.Function */
36 | .nl { color: #A0A000 } /* Name.Label */
37 | .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
38 | .nt { color: #008000; font-weight: bold } /* Name.Tag */
39 | .nv { color: #19177C } /* Name.Variable */
40 | .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
41 | .w { color: #bbbbbb } /* Text.Whitespace */
42 | .mb { color: #666666 } /* Literal.Number.Bin */
43 | .mf { color: #666666 } /* Literal.Number.Float */
44 | .mh { color: #666666 } /* Literal.Number.Hex */
45 | .mi { color: #666666 } /* Literal.Number.Integer */
46 | .mo { color: #666666 } /* Literal.Number.Oct */
47 | .sb { color: #BA2121 } /* Literal.String.Backtick */
48 | .sc { color: #BA2121 } /* Literal.String.Char */
49 | .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
50 | .s2 { color: #BA2121 } /* Literal.String.Double */
51 | .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
52 | .sh { color: #BA2121 } /* Literal.String.Heredoc */
53 | .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
54 | .sx { color: #008000 } /* Literal.String.Other */
55 | .sr { color: #BB6688 } /* Literal.String.Regex */
56 | .s1 { color: #BA2121 } /* Literal.String.Single */
57 | .ss { color: #19177C } /* Literal.String.Symbol */
58 | .bp { color: #008000 } /* Name.Builtin.Pseudo */
59 | .vc { color: #19177C } /* Name.Variable.Class */
60 | .vg { color: #19177C } /* Name.Variable.Global */
61 | .vi { color: #19177C } /* Name.Variable.Instance */
62 | .il { color: #666666 } /* Literal.Number.Integer.Long */
63 |
64 | li.line {
65 | font-weight: normal;
66 | vertical-align: top;
67 | color: rgb(175, 175, 175);
68 | font-size: 1em;
69 | border-left: 3px solid #6CE26C !important;
70 | padding-left: 10px;
71 | white-space: pre-wrap;
72 | }
73 |
74 | div.line {
75 | color: black;
76 | }
77 |
78 | ol.code {
79 | font-family: monospace;
80 | }
--------------------------------------------------------------------------------
/static/css/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+actionscript+apacheconf+applescript+aspnet+autohotkey+bash+c+csharp+cpp+coffeescript+css-extras+dart+eiffel+erlang+fsharp+fortran+gherkin+git+go+groovy+haml+handlebars+haskell+http+ini+jade+java+julia+latex+less+lolcode+markdown+matlab+nasm+nsis+objectivec+pascal+perl+php+php-extras+powershell+python+r+jsx+rest+rip+ruby+rust+sas+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+twig+typescript+wiki+yaml&plugins=line-numbers */
2 | /**
3 | * prism.js default theme for JavaScript, CSS and HTML
4 | * Based on dabblet (http://dabblet.com)
5 | * @author Lea Verou
6 | */
7 |
8 | code[class*="language-"],
9 | pre[class*="language-"] {
10 | color: black;
11 | text-shadow: 0 1px white;
12 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
13 | direction: ltr;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | line-height: 1.5;
19 |
20 | -moz-tab-size: 4;
21 | -o-tab-size: 4;
22 | tab-size: 4;
23 |
24 | -webkit-hyphens: none;
25 | -moz-hyphens: none;
26 | -ms-hyphens: none;
27 | hyphens: none;
28 | }
29 |
30 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
31 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
32 | text-shadow: none;
33 | background: white;
34 | }
35 |
36 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
37 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
38 | text-shadow: none;
39 | background: white;
40 | }
41 |
42 | @media print {
43 | code[class*="language-"],
44 | pre[class*="language-"] {
45 | text-shadow: none;
46 | }
47 | }
48 |
49 | /* Code blocks */
50 | pre[class*="language-"] {
51 | padding: 1em;
52 | margin: .5em 0;
53 | overflow: auto;
54 | }
55 |
56 | :not(pre) > code[class*="language-"],
57 | pre[class*="language-"] {
58 | background: #f5f2f0;
59 | }
60 |
61 | /* Inline code */
62 | :not(pre) > code[class*="language-"] {
63 | padding: .1em;
64 | border-radius: .3em;
65 | }
66 |
67 | .token.comment,
68 | .token.prolog,
69 | .token.doctype,
70 | .token.cdata {
71 | color: slategray;
72 | }
73 |
74 | .token.punctuation {
75 | color: #999;
76 | }
77 |
78 | .namespace {
79 | opacity: .7;
80 | }
81 |
82 | .token.property,
83 | .token.tag,
84 | .token.boolean,
85 | .token.number,
86 | .token.constant,
87 | .token.symbol,
88 | .token.deleted {
89 | color: #905;
90 | }
91 |
92 | .token.selector,
93 | .token.attr-name,
94 | .token.string,
95 | .token.char,
96 | .token.builtin,
97 | .token.inserted {
98 | color: #690;
99 | }
100 |
101 | .token.operator,
102 | .token.entity,
103 | .token.url,
104 | .language-css .token.string,
105 | .style .token.string {
106 | color: #a67f59;
107 | background: hsla(0, 0%, 100%, .5);
108 | }
109 |
110 | .token.atrule,
111 | .token.attr-value,
112 | .token.keyword {
113 | color: #07a;
114 | }
115 |
116 | .token.function {
117 | color: #DD4A68;
118 | }
119 |
120 | .token.regex,
121 | .token.important,
122 | .token.variable {
123 | color: #e90;
124 | }
125 |
126 | .token.important,
127 | .token.bold {
128 | font-weight: bold;
129 | }
130 | .token.italic {
131 | font-style: italic;
132 | }
133 |
134 | .token.entity {
135 | cursor: help;
136 | }
137 |
138 | pre.line-numbers {
139 | position: relative;
140 | padding-left: 3.8em;
141 | counter-reset: linenumber;
142 | }
143 |
144 | pre.line-numbers > code {
145 | position: relative;
146 | }
147 |
148 | .line-numbers .line-numbers-rows {
149 | position: absolute;
150 | pointer-events: none;
151 | top: 0;
152 | font-size: 100%;
153 | left: -3.8em;
154 | width: 3em; /* works for line-numbers below 1000 lines */
155 | letter-spacing: -1px;
156 | border-right: 3px solid #6CE26C;
157 |
158 | -webkit-user-select: none;
159 | -moz-user-select: none;
160 | -ms-user-select: none;
161 | user-select: none;
162 |
163 | }
164 |
165 | .line-numbers-rows > span {
166 | pointer-events: none;
167 | display: block;
168 | counter-increment: linenumber;
169 | }
170 |
171 | .line-numbers-rows > span:before {
172 | content: counter(linenumber);
173 | color: #999;
174 | display: block;
175 | padding-right: 0.8em;
176 | text-align: right;
177 | }
178 |
--------------------------------------------------------------------------------
/static/js/pastebin-submit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This script is responsible for submitting the paste as well as performing form validation
3 | */
4 | if (typeof pastebin === 'undefined') {
5 | var pastebin = {};
6 | pastebin.urls = {};
7 | }
8 |
9 | pastebin.urls["submit_paste"] = window.location.protocol + "//" + window.location.host;
10 |
11 | var pastebin_paste_encrypted = false;
12 |
13 | /**
14 | * Called when the script is loaded
15 | */
16 | pastebin.loadSubmitForm = function() {
17 | // Add event handlers
18 | $("#encryption-password").change(function() { pastebin.updateEncryptionTab(); });
19 | $("#encryption-password").keydown(function() { pastebin.updateEncryptionTab(); });
20 | $("#encryption-password").keyup(function() { pastebin.updateEncryptionTab(); });
21 |
22 | $("#encryption-confirm-password").change(function() { pastebin.updateEncryptionTab(); });
23 | $("#encryption-confirm-password").keydown(function() { pastebin.updateEncryptionTab(); });
24 | $("#encryption-confirm-password").keyup(function() { pastebin.updateEncryptionTab(); });
25 |
26 | $("#encryption-generate-password").click(function() { pastebin.generateRandomPassword(); });
27 | $("#encryption-encrypt").click(function() { pastebin.encryptPaste(); });
28 |
29 | $("#encryption-show-password").click(function() { pastebin.setPasswordVisible(this.checked); });
30 | };
31 |
32 | /**
33 | * Update the encryption tab
34 | */
35 | pastebin.updateEncryptionTab = function() {
36 | if (pastebin_paste_encrypted) {
37 | $("#encryption-password").attr("disabled", true);
38 | $("#encryption-confirm-password").attr("disabled", true);
39 | $("#encryption-generate-password").attr("disabled", true);
40 | $("#encryption-encrypt").attr("disabled", true);
41 | return;
42 | }
43 |
44 | var password = $("#encryption-password").val();
45 | var confirmPassword = $("#encryption-confirm-password").val();
46 |
47 | if (password === confirmPassword && password != "") {
48 | $("#encryption-encrypt").attr("disabled", false);
49 |
50 | $("#encryption-password-group").removeClass("has-error");
51 | $("#encryption-confirm-password-group").removeClass("has-error");
52 |
53 | $("#encryption-password-group").addClass("has-success");
54 | $("#encryption-confirm-password-group").addClass("has-success");
55 | } else {
56 | $("#encryption-encrypt").attr("disabled", true);
57 |
58 | $("#encryption-password-group").addClass("has-error");
59 | $("#encryption-confirm-password-group").addClass("has-error");
60 |
61 | $("#encryption-password-group").removeClass("has-success");
62 | $("#encryption-confirm-password-group").removeClass("has-success");
63 | }
64 | };
65 |
66 | /**
67 | * Generates a random password
68 | */
69 | pastebin.generateRandomPassword = function() {
70 | var chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
71 |
72 | var password = "";
73 |
74 | for (var i=0; i < 16; i++) {
75 | // Use random word generator provided courtesy of SJCL library
76 | var char = chars[(Math.abs(sjcl.random.randomWords(1))%chars.length)];
77 | password += char;
78 | }
79 |
80 | $("#encryption-password").val(password);
81 | $("#encryption-confirm-password").val(password);
82 |
83 | pastebin.setPasswordVisible(true);
84 | pastebin.updateEncryptionTab();
85 |
86 | $("#encryption-show-password").prop("checked", true);
87 | $("#encryption-password").select();
88 | };
89 |
90 | /**
91 | * Set whether passwords should be visible or not
92 | */
93 | pastebin.setPasswordVisible = function(visible) {
94 | if (visible) {
95 | $("#encryption-password").attr("type", "text");
96 | $("#encryption-confirm-password").attr("type", "text");
97 | } else {
98 | $("#encryption-password").attr("type", "password");
99 | $("#encryption-confirm-password").attr("type", "password");
100 | }
101 | }
102 |
103 | /**
104 | * Encrypt the paste and prevent it from being edited again
105 | */
106 | pastebin.encryptPaste = function() {
107 | var text = $("[name='text']").val();
108 | var password = $("#encryption-password").val();
109 |
110 | text = sjcl.encrypt(password, text);
111 |
112 | $("[name='text']").val(text).attr("readonly", true);
113 | $("[name='encrypted']").val(true);
114 |
115 | pastebin_paste_encrypted = true;
116 |
117 | pastebin.updateEncryptionTab();
118 | };
119 |
120 | pastebin.loadSubmitForm();
--------------------------------------------------------------------------------
/home/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, redirect
2 | from django.db.models import Q
3 | from django.core.cache import cache
4 |
5 | from django.views.decorators.cache import cache_page
6 |
7 | from django.utils import timezone
8 |
9 | from pastes.forms import SubmitPasteForm
10 | from pastes.models import Paste, LatestPastes
11 |
12 | from pastebin.util import Paginator
13 |
14 | from users.models import Limiter
15 |
16 | import highlighting
17 | import math
18 |
19 | def home(request):
20 | """
21 | Display the index page with the form to submit a paste, as well as the most recent
22 | pastes
23 | """
24 | paste_form = SubmitPasteForm(request.POST or None, request=request)
25 |
26 | latest_pastes = cache.get("home_latest_pastes")
27 |
28 | if latest_pastes == None:
29 | latest_pastes = Paste.objects.get_pastes(include_expired=False, include_hidden=False,
30 | count=15)
31 | cache.set("home_latest_pastes", latest_pastes, 5)
32 |
33 | languages = highlighting.settings.LANGUAGES
34 |
35 | if paste_form.is_valid():
36 | paste_data = paste_form.cleaned_data
37 |
38 | user = None
39 | if request.user.is_authenticated():
40 | user = request.user
41 |
42 | paste = Paste()
43 |
44 | char_id = paste.add_paste(title=paste_data["title"],
45 | user=user,
46 | text=paste_data["text"],
47 | expiration=paste_data["expiration"],
48 | visibility=paste_data["visibility"],
49 | format=paste_data["syntax_highlighting"],
50 | encrypted=paste_data["encrypted"])
51 |
52 | Limiter.increase_action_count(request, Limiter.PASTE_UPLOAD)
53 |
54 | # Redirect to the newly created paste
55 | return redirect("show_paste", char_id=char_id)
56 |
57 | return render(request, "home/home.html", {"form": paste_form,
58 | "latest_pastes": latest_pastes,
59 | "languages": languages })
60 |
61 | def latest_pastes(request, page=1):
62 | """
63 | Show all of the pastes starting from the newest
64 | """
65 | PASTES_PER_PAGE = 15
66 |
67 | page = int(page)
68 |
69 | current_datetime = timezone.now()
70 |
71 | total_paste_count = cache.get("total_latest_pastes_count")
72 |
73 | if total_paste_count == None:
74 | total_paste_count = Paste.objects.filter(hidden=False).filter(Q(expiration_datetime__isnull=True) | Q(expiration_datetime__gte=current_datetime)).count()
75 | cache.set("total_latest_pastes_count", total_paste_count)
76 |
77 | total_pages = int(math.ceil(float(total_paste_count) / float(PASTES_PER_PAGE)))
78 | if page > total_pages:
79 | page = max(int(total_pages), 1)
80 |
81 | offset = (page-1) * PASTES_PER_PAGE
82 | pastes = cache.get("latest_pastes:%s" % page)
83 |
84 | if pastes == None:
85 | pastes = Paste.objects.get_pastes(count=PASTES_PER_PAGE, offset=offset, include_hidden=False)
86 | cache.set("latest_pastes:%s" % page, pastes, 5)
87 |
88 | pages = Paginator.get_pages(page, PASTES_PER_PAGE, total_paste_count)
89 | total_pages = math.ceil(float(total_paste_count) / float(PASTES_PER_PAGE))
90 |
91 | return render(request, "latest_pastes/latest_pastes.html", {"current_page": page,
92 | "pastes": pastes,
93 | "pages": pages,
94 | "total_pages": total_pages,
95 | "total_paste_count": total_paste_count})
96 |
97 | def faq(request):
98 | return render(request, "home/faq.html")
99 |
100 | def random_paste(request):
101 | """
102 | Redirect to a random paste
103 | """
104 | char_id = Paste.get_random_char_id()
105 |
106 | if char_id:
107 | return redirect("show_paste", char_id=char_id)
108 | else:
109 | return redirect("home:home")
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | INSTALLING AND RUNNING PASTEBIN-DJANGO
2 | ===
3 | These steps explain how to get pastebin-django working in such a way that it can be launched using the provided manage.py script (which is unsuitable for production use!), the database connection works correctly, etc. There are multiple ways to run Django projects in a production environment, although for pastesite.matoking.com I've used uWSGI with nginx. Instructions for running a Django application with that stack can be found here:
4 | https://uwsgi-docs.readthedocs.org/en/latest/tutorials/Django_and_nginx.html
5 |
6 |
7 | Installing dependencies
8 | --
9 | pastebin-django requires Python and a few related dependencies to be installed (virtualenv, pip). The following command should install the required dependencies if you are running on Debian or a derivative (eg. Linux Mint, Ubuntu).
10 |
11 | sudo apt-get install python python-dev python-pip python-virtualenv
12 |
13 | Creating virtualenv container and installing required Python libraries
14 | --
15 | Although this step is optional, it's recommended to install and run the web application inside a virtualenv environment, as this isolates the web application's environment from the system-wide Python installation, thus ensuring that your web application won't be accidentally broken by a system-wide update.
16 |
17 | To create the virtualenv environment, run the following command on the pastebin-django directory, which contains the project's apps such as pastes, comments.
18 |
19 | virtualenv pastebin-django
20 |
21 | Once you have created the virtualenv environment, you can start using it by running the following command.
22 |
23 | source bin/activate
24 |
25 | You can always deactivate the virtualenv environment by running the following command.
26 |
27 | deactivate
28 |
29 | But instead of leaving the environment, let's install the required Python libraries using pip. cd inside the pastebin-django directory and install the required Python libraries. Note that sudo isn't necessary, as we are installing all of the libraries inside our isolated Python environment.
30 |
31 | cd pastebin-django
32 | pip install -r requirements.txt
33 |
34 | Configuring the PostgreSQL database
35 | --
36 | We'll assume you have already created a database and a role which can access the said database. Start by opening the settings.py file in pastebin/settings.py and changing the credentials in DATABASES['default']. If you're going to be running unit tests, you can change the database name in DATABASES['default']['TEST']['NAME'], which is the database that will be used when running the unit tests.
37 |
38 | After this is done, run the following command in the root of your virtualenv environment to create Django's in-built database tables. You may also be prompted to create a superuser, which you can use when logging into pastebin-django.
39 |
40 | python manage.py syncdb
41 |
42 | Configuring the Redis instance
43 | --
44 | pastebin-django uses a data structure server to both to store persistent data that wouldn't be a good fit for a relational database (eg. paste hit counts). You can install Redis on Debian or a derivative using the following command.
45 |
46 | sudo apt-get install redis-server
47 |
48 | By default Redis runs on port 6379 and saves its data regularly. However, pastebin-django's default settings assume a persistent Redis storage runs on the port 6380, so you may need to change the port for the 'persistent' cache in settings.py to match this port. You can also run a non-persistent Redis server under the port 6379 and a persistent Redis server under port 6380, which matches the default settings.
49 |
50 | Running the unit tests
51 | --
52 | At this point your web application should be configured correctly. But to make sure that everything will work nicely before we try running the web application, run the unit tests using the following command. You can use your "normal" database when running the unit tests, but you'll need to recreate the tables described in sql/create_tables.sql after running the tests, as those will be automatically dropped.
53 |
54 | python manage.py test
55 |
56 | If everything worked as intended, all of the tests should pass which means the web application <--> database connection is working correctly.
57 |
58 | Starting the development web server
59 | --
60 | We are now ready to start the development web server. Run the following command to run the web application on 127.0.0.1:8000, which is also the URL you'll need to enter into your web browser in order to access the site. Feel free to change the address and/or port depending on your working environment.
61 |
62 | python manage.py runserver 127.0.0.1:8000
63 |
64 | Now, try opening http://127.0.0.1:8000 in your web browser. If everything worked out correctly, you should now be able to use the web application as normal.
65 |
--------------------------------------------------------------------------------
/static/css/pastebin-django.css:
--------------------------------------------------------------------------------
1 | .paste-text-field {
2 | font-family: Consolas, "DejaVu Sans Mono", "Lucida Console", Monaco, monospace;
3 | font-size: 10pt;
4 | resize: none;
5 | }
6 |
7 | .paste-text {
8 | font-family: Consolas, "DejaVu Sans Mono", "Lucida Console", Monaco, monospace;
9 | font-size: 10pt;
10 | }
11 |
12 | .submit-paste-tabs {
13 | margin-bottom: 15px;
14 | }
15 |
16 | .comment-text-field {
17 | resize: none;
18 | }
19 |
20 | .latest-pastes-table {
21 | font-size: 8pt;
22 | }
23 |
24 | /* Class that removes the styling Bootstrap normally applies to pre elements */
25 | .pre-code {
26 | background-color: white !important;
27 | border: 0px;
28 | padding-top: 0px !important;
29 | padding-bottom: 0px !important;
30 | overflow-y: hidden !important;
31 | }
32 |
33 | /* form-actions was removed in Bootstrap 3, so add it back here */
34 | .form-actions {
35 | padding: 17px 0px 18px;
36 | margin-top: 18px;
37 | margin-bottom: 18px;
38 | background-color: #F5F5F5;
39 | border-top: 1px solid #E5E5E5;
40 | }
41 |
42 | /* Loading spinner */
43 | .loader {
44 | font-size: 8px;
45 | margin: 5em auto;
46 | width: 1em;
47 | height: 1em;
48 | border-radius: 50%;
49 | position: relative;
50 | text-indent: -9999em;
51 | -webkit-animation: load4 1.3s infinite linear;
52 | animation: load4 1.3s infinite linear;
53 | -webkit-transform: translateZ(0);
54 | -ms-transform: translateZ(0);
55 | transform: translateZ(0);
56 | }
57 |
58 | @-webkit-keyframes load4 {
59 | 0%,
60 | 100% {
61 | box-shadow: 0em -3em 0em 0.2em #000000, 2em -2em 0 0em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 0em #000000;
62 | }
63 | 12.5% {
64 | box-shadow: 0em -3em 0em 0em #000000, 2em -2em 0 0.2em #000000, 3em 0em 0 0em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
65 | }
66 | 25% {
67 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 0em #000000, 3em 0em 0 0.2em #000000, 2em 2em 0 0em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
68 | }
69 | 37.5% {
70 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 0em #000000, 2em 2em 0 0.2em #000000, 0em 3em 0 0em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
71 | }
72 | 50% {
73 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 0em #000000, 0em 3em 0 0.2em #000000, -2em 2em 0 0em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
74 | }
75 | 62.5% {
76 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 0em #000000, -2em 2em 0 0.2em #000000, -3em 0em 0 0em #000000, -2em -2em 0 -0.5em #000000;
77 | }
78 | 75% {
79 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 0em #000000, -3em 0em 0 0.2em #000000, -2em -2em 0 0em #000000;
80 | }
81 | 87.5% {
82 | box-shadow: 0em -3em 0em 0em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 0em #000000, -3em 0em 0 0em #000000, -2em -2em 0 0.2em #000000;
83 | }
84 | }
85 | @keyframes load4 {
86 | 0%,
87 | 100% {
88 | box-shadow: 0em -3em 0em 0.2em #000000, 2em -2em 0 0em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 0em #000000;
89 | }
90 | 12.5% {
91 | box-shadow: 0em -3em 0em 0em #000000, 2em -2em 0 0.2em #000000, 3em 0em 0 0em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
92 | }
93 | 25% {
94 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 0em #000000, 3em 0em 0 0.2em #000000, 2em 2em 0 0em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
95 | }
96 | 37.5% {
97 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 0em #000000, 2em 2em 0 0.2em #000000, 0em 3em 0 0em #000000, -2em 2em 0 -0.5em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
98 | }
99 | 50% {
100 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 0em #000000, 0em 3em 0 0.2em #000000, -2em 2em 0 0em #000000, -3em 0em 0 -0.5em #000000, -2em -2em 0 -0.5em #000000;
101 | }
102 | 62.5% {
103 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 0em #000000, -2em 2em 0 0.2em #000000, -3em 0em 0 0em #000000, -2em -2em 0 -0.5em #000000;
104 | }
105 | 75% {
106 | box-shadow: 0em -3em 0em -0.5em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 0em #000000, -3em 0em 0 0.2em #000000, -2em -2em 0 0em #000000;
107 | }
108 | 87.5% {
109 | box-shadow: 0em -3em 0em 0em #000000, 2em -2em 0 -0.5em #000000, 3em 0em 0 -0.5em #000000, 2em 2em 0 -0.5em #000000, 0em 3em 0 -0.5em #000000, -2em 2em 0 0em #000000, -3em 0em 0 0em #000000, -2em -2em 0 0.2em #000000;
110 | }
111 | }
--------------------------------------------------------------------------------
/pastes/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from pastebin import settings
4 | from pastes.models import Paste
5 | from users.models import Limiter
6 |
7 | from humanfriendly import format_timespan
8 |
9 | import highlighting
10 |
11 | class SubmitPasteForm(forms.Form):
12 | """
13 | Form to submit the paste
14 |
15 | Contains paste text, title and optionally, time until expiration
16 | """
17 | VISIBILITY_CHOICES = (
18 | (Paste.PUBLIC, "Public"),
19 | (Paste.HIDDEN, "Hidden")
20 | )
21 |
22 | EXPIRATION_CHOICES = (
23 | (Paste.NEVER, "Never"),
24 | (Paste.FIFTEEN_MINUTES, "15 minutes"),
25 | (Paste.ONE_HOUR, "1 hour"),
26 | (Paste.ONE_DAY, "1 day"),
27 | (Paste.ONE_WEEK, "1 week"),
28 | (Paste.ONE_MONTH, "1 month"),
29 | )
30 |
31 | title = forms.CharField(max_length=128,
32 | required=False,
33 | widget=forms.TextInput(attrs={"placeholder": "Untitled"}))
34 | text = forms.CharField(min_length=1,
35 | max_length=100000,
36 | error_messages={"required": "The paste can't be empty."})
37 | expiration = forms.ChoiceField(choices=EXPIRATION_CHOICES)
38 |
39 | visibility = forms.ChoiceField(choices=VISIBILITY_CHOICES)
40 |
41 | syntax_highlighting = forms.ChoiceField(choices=highlighting.settings.LANGUAGES,
42 | help_text="Languages marked with * are also supported with encrypted pastes.")
43 |
44 | encrypted = forms.BooleanField(initial=False,
45 | widget=forms.HiddenInput(),
46 | required=False)
47 |
48 | def __init__(self, *args, **kwargs):
49 | self.request = kwargs.pop('request', None)
50 |
51 | if self.request == None:
52 | raise AttributeError("'%s' requires a valid Django request object as its request parameter" % self.__class__.__name__)
53 |
54 | super(SubmitPasteForm, self).__init__(*args, **kwargs)
55 |
56 | def clean_title(self):
57 | """
58 | Replace the title with Untitled if it is not provided
59 | """
60 | title = self.cleaned_data.get("title")
61 |
62 | # If user provides an empty title, replace it with Untitled
63 | if title.strip() == "":
64 | title = "Untitled"
65 |
66 | return title
67 |
68 | def clean_text(self):
69 | """
70 | Check that the user hasn't uploaded too many pastes
71 | """
72 | if Limiter.is_limit_reached(self.request, Limiter.PASTE_UPLOAD):
73 | action_limit = Limiter.get_action_limit(self.request, Limiter.PASTE_UPLOAD)
74 |
75 | raise forms.ValidationError("You can only upload %s pastes every %s." % (action_limit, format_timespan(settings.MAX_PASTE_UPLOADS_PERIOD)))
76 |
77 | return self.cleaned_data.get("text")
78 |
79 | class EditPasteForm(forms.Form):
80 | """
81 | Form to edit the paste
82 | """
83 | note = forms.CharField(max_length=1024,
84 | required=False,
85 | help_text="Optional note describing what was changed in the paste")
86 | title = forms.CharField(max_length=128,
87 | required=False,
88 | widget=forms.TextInput(attrs={"placeholder": "Untitled"}))
89 | visibility = forms.ChoiceField(choices=SubmitPasteForm.VISIBILITY_CHOICES)
90 |
91 | syntax_highlighting = forms.ChoiceField(choices=highlighting.settings.LANGUAGES,
92 | help_text="Languages marked with * are also supported with encrypted pastes.")
93 | text = forms.CharField(min_length=1,
94 | max_length=100000,
95 | error_messages={"required": "The paste can't be empty."})
96 |
97 | encrypted = forms.BooleanField(initial=False,
98 | widget=forms.HiddenInput(),
99 | required=False)
100 |
101 | def __init__(self, *args, **kwargs):
102 | self.request = kwargs.pop('request', None)
103 |
104 | if self.request == None:
105 | raise AttributeError("'%s' requires a valid Django request object as its request parameter" % self.__class__.__name__)
106 |
107 | super(EditPasteForm, self).__init__(*args, **kwargs)
108 |
109 | def clean_title(self):
110 | """
111 | Replace an empty title with "Untitled"
112 | """
113 | title = self.cleaned_data.get("title")
114 |
115 | if title.strip() == "":
116 | title = "Untitled"
117 |
118 | return title
119 |
120 | def clean_text(self):
121 | """
122 | Check that the user hasn't edited too many pastes
123 | """
124 | if Limiter.is_limit_reached(self.request, Limiter.PASTE_EDIT):
125 | action_limit = Limiter.get_action_limit(self.request, Limiter.PASTE_EDIT)
126 |
127 | raise forms.ValidationError("You can only edit pastes %s times every %s." % (action_limit, format_timespan(settings.MAX_PASTE_EDITS_PERIOD)))
128 |
129 | return self.cleaned_data.get("text")
130 |
131 | class RemovePasteForm(forms.Form):
132 | """
133 | Form to remove the paste
134 | """
135 | removal_reason = forms.CharField(max_length=512,
136 | required=False,
137 | help_text="You can provide a reason why you removed the paste.")
138 |
139 | class ReportPasteForm(forms.Form):
140 | """
141 | Form to report a paste
142 | """
143 | REASONS = (
144 | ("illegal_content", "Illegal content"),
145 | ("adult_content", "Adult content"),
146 | ("spam", "Spam"),
147 | ("personal_information", "Personal information"),
148 | ("other", "Other")
149 | )
150 |
151 | reason = forms.ChoiceField(choices=REASONS)
152 | text = forms.CharField(min_length=1,
153 | max_length=4096,
154 | required=False,
155 | widget=forms.Textarea)
--------------------------------------------------------------------------------
/users/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from django.contrib.auth.models import User
4 | from django.contrib.auth import authenticate
5 |
6 | from django.core.urlresolvers import reverse, NoReverseMatch
7 |
8 | from pastes.models import Paste
9 |
10 | class RegisterForm(forms.Form):
11 | """
12 | User registration form
13 | """
14 | username = forms.CharField(max_length=64,
15 | required=True)
16 | password = forms.CharField(min_length=6,
17 | max_length=128,
18 | widget = forms.TextInput(attrs={ 'type': 'password' }))
19 | confirm_password = forms.CharField(min_length=6,
20 | max_length=128,
21 | widget = forms.TextInput(attrs={ 'type': 'password' }))
22 |
23 | def clean_username(self):
24 | """
25 | Check that the username isn't already in use and that it doesn't conflict
26 | with an existing URL
27 | """
28 | username = self.cleaned_data.get("username")
29 |
30 | # Ehh, nobody will notice
31 | if User.objects.filter(username__iexact=username).count() > 0:
32 | # The username exists
33 | raise forms.ValidationError("The username is already in use.")
34 |
35 | # Make sure the username doesn't conflict with the URLs
36 | url_conflict = True
37 | try:
38 | test = reverse('users:' + username)
39 | except NoReverseMatch:
40 | url_conflict = False
41 |
42 | if url_conflict:
43 | # The username can't be used because it conflicts with an URL
44 | raise forms.ValidationError("This username can't be used.")
45 |
46 | return username
47 |
48 | def clean_password(self):
49 | password = self.cleaned_data.get("password")
50 |
51 | if password == "correct horse battery staple":
52 | raise forms.ValidationError("You'll have to pick a better password than that.")
53 |
54 | return password
55 |
56 | def clean_confirm_password(self):
57 | """
58 | Check that the provided passwords match
59 | """
60 | password = self.cleaned_data.get("password")
61 | confirm_password = self.cleaned_data.get("confirm_password")
62 |
63 | if password != confirm_password:
64 | raise forms.ValidationError("The provided password didn't match.")
65 |
66 | return confirm_password
67 |
68 | class LoginForm(forms.Form):
69 | """
70 | User login form
71 | """
72 | username = forms.CharField(max_length=64)
73 | password = forms.CharField(max_length=128,
74 | widget = forms.TextInput(attrs={ 'type': 'password' }))
75 |
76 | class ChangePreferencesForm(forms.Form):
77 | """
78 | Form to change user's profile preferences
79 | """
80 | public_favorites = forms.BooleanField(required=False,
81 | help_text="Allow everyone to see your favorited pastes")
82 |
83 | class ChangePasswordForm(forms.Form):
84 | """
85 | Form to change the user's password
86 | """
87 | current_password = forms.CharField(max_length=128,
88 | widget=forms.TextInput(attrs={'type': 'password'}))
89 |
90 | new_password = forms.CharField(min_length=6,
91 | max_length=128,
92 | widget=forms.TextInput(attrs={'type': 'password'}))
93 | confirm_new_password = forms.CharField(min_length=6,
94 | max_length=128,
95 | widget=forms.TextInput(attrs={'type': 'password'}))
96 |
97 | def __init__(self, *args, **kwargs):
98 | self.user = kwargs.pop('user', None)
99 |
100 | if self.user == None:
101 | raise AttributeError("'%s' requires a valid Django user object as its user parameter" % self.__class__.__name__)
102 |
103 | super(ChangePasswordForm, self).__init__(*args, **kwargs)
104 |
105 | def clean_current_password(self):
106 | """
107 | Check that the user has logged in and provided the correct password
108 | """
109 | if not self.user or not self.user.is_authenticated():
110 | raise forms.ValidationError("You are not logged in.")
111 |
112 | password = self.cleaned_data.get('current_password')
113 |
114 | if authenticate(username=self.user.username, password=password) == None:
115 | raise forms.ValidationError("The provided password was not correct")
116 |
117 | return password
118 |
119 | def clean_confirm_new_password(self):
120 | """
121 | Check that the provided new passwords match
122 | """
123 | new_password = self.cleaned_data.get("new_password")
124 | confirm_new_password = self.cleaned_data.get("confirm_new_password")
125 |
126 | if new_password != confirm_new_password:
127 | raise forms.ValidationError("The provided passwords didn't match.")
128 |
129 | return confirm_new_password
130 |
131 | class VerifyPasswordForm(forms.Form):
132 | """
133 | Form to verify the user's password
134 | """
135 | password = forms.CharField(max_length=128,
136 | widget=forms.TextInput(attrs={ 'type': 'password' }),
137 | help_text="You need to enter your password to perform this action.")
138 |
139 | def __init__(self, *args, **kwargs):
140 | self.user = kwargs.pop('user', None)
141 |
142 | if self.user == None:
143 | raise AttributeError("'%s' requires a valid Django user object as its user parameter" % self.__class__.__name__)
144 |
145 | super(VerifyPasswordForm, self).__init__(*args, **kwargs)
146 |
147 | def clean_password(self):
148 | """
149 | Check that the user has logged in and provided the correct password
150 | """
151 | if not self.user or not self.user.is_authenticated():
152 | raise forms.ValidationError("You are not logged in.")
153 |
154 | password = self.cleaned_data.get('password')
155 |
156 | if authenticate(username=self.user.username, password=password) == None:
157 | raise forms.ValidationError("The provided password was not correct.")
158 |
159 | return password
--------------------------------------------------------------------------------
Post a comment
5 |