##// END OF EJS Templates
Implemented bumplimit.
neko259 -
r38:3006249c default
parent child Browse files
Show More
@@ -1,208 +1,214 b''
1 import os
1 import os
2 import re
2 import re
3 from django.db import models
3 from django.db import models
4 from django.utils import timezone
4 from django.utils import timezone
5 import time
5 import time
6
6
7 from neboard import settings
7 from neboard import settings
8
8
9 import thumbs
9 import thumbs
10
10
11 NO_PARENT = -1
11 NO_PARENT = -1
12 NO_IP = '0.0.0.0'
12 NO_IP = '0.0.0.0'
13 UNKNOWN_UA = ''
13 UNKNOWN_UA = ''
14
14
15
15
16 def update_image_filename(instance, filename):
16 def update_image_filename(instance, filename):
17 """Get unique image filename"""
17 """Get unique image filename"""
18
18
19 path = 'images/'
19 path = 'images/'
20 new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename
20 new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename
21 return os.path.join(path, new_name)
21 return os.path.join(path, new_name)
22
22
23
23
24 class PostManager(models.Manager):
24 class PostManager(models.Manager):
25 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
25 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
26 ip=NO_IP, tags=None):
26 ip=NO_IP, tags=None):
27 post = self.create(title=title,
27 post = self.create(title=title,
28 text=text,
28 text=text,
29 pub_time=timezone.now(),
29 pub_time=timezone.now(),
30 parent=parent_id,
30 parent=parent_id,
31 image=image,
31 image=image,
32 poster_ip=ip,
32 poster_ip=ip,
33 poster_user_agent=UNKNOWN_UA,
33 poster_user_agent=UNKNOWN_UA,
34 last_edit_time=timezone.now())
34 last_edit_time=timezone.now())
35
35
36 if tags:
36 if tags:
37 for tag in tags:
37 for tag in tags:
38 post.tags.add(tag)
38 post.tags.add(tag)
39
39
40 if parent_id != NO_PARENT:
40 if parent_id != NO_PARENT:
41 parent = self.get(id=parent_id)
41 self._bump_thread(parent_id)
42 parent.last_edit_time=timezone.now()
43 parent.save()
44 else:
42 else:
45 self._delete_old_threads()
43 self._delete_old_threads()
46
44
47 return post
45 return post
48
46
49 def delete_post(self, post):
47 def delete_post(self, post):
50 children = self.filter(parent=post.id)
48 children = self.filter(parent=post.id)
51 for child in children:
49 for child in children:
52 self.delete_post(child)
50 self.delete_post(child)
53 post.delete()
51 post.delete()
54
52
55 def delete_posts_by_ip(self, ip):
53 def delete_posts_by_ip(self, ip):
56 posts = self.filter(poster_ip=ip)
54 posts = self.filter(poster_ip=ip)
57 for post in posts:
55 for post in posts:
58 self.delete_post(post)
56 self.delete_post(post)
59
57
60 def get_threads(self, tag=None):
58 def get_threads(self, tag=None):
61 if tag:
59 if tag:
62 threads = self.filter(parent=NO_PARENT, tags=tag)
60 threads = self.filter(parent=NO_PARENT, tags=tag)
63 else:
61 else:
64 threads = self.filter(parent=NO_PARENT)
62 threads = self.filter(parent=NO_PARENT)
65 threads = list(threads.order_by('-last_edit_time'))
63 threads = list(threads.order_by('-last_edit_time'))
66
64
67 return threads
65 return threads
68
66
69 def get_thread(self, opening_post_id):
67 def get_thread(self, opening_post_id):
70 opening_post = self.get(id=opening_post_id)
68 opening_post = self.get(id=opening_post_id)
71
69
72 if opening_post.parent == NO_PARENT:
70 if opening_post.parent == NO_PARENT:
73 replies = self.filter(parent=opening_post_id)
71 replies = self.filter(parent=opening_post_id)
74
72
75 thread = [opening_post]
73 thread = [opening_post]
76 thread.extend(replies)
74 thread.extend(replies)
77
75
78 return thread
76 return thread
79
77
80 def exists(self, post_id):
78 def exists(self, post_id):
81 posts = self.filter(id=post_id)
79 posts = self.filter(id=post_id)
82
80
83 return len(posts) > 0
81 return len(posts) > 0
84
82
85 def _delete_old_threads(self):
83 def _delete_old_threads(self):
86 """
84 """
87 Preserves maximum thread count. If there are too many threads,
85 Preserves maximum thread count. If there are too many threads,
88 delete the old ones.
86 delete the old ones.
89 """
87 """
90
88
91 # TODO Try to find a better way to get the active thread count.
89 # TODO Try to find a better way to get the active thread count.
92
90
93 # TODO Move old threads to the archive instead of deleting them.
91 # TODO Move old threads to the archive instead of deleting them.
94 # Maybe make some 'old' field in the model to indicate the thread
92 # Maybe make some 'old' field in the model to indicate the thread
95 # must not be shown and be able for replying.
93 # must not be shown and be able for replying.
96
94
97 threads = self.get_threads()
95 threads = self.get_threads()
98 thread_count = len(threads)
96 thread_count = len(threads)
99
97
100 if thread_count > settings.MAX_THREAD_COUNT:
98 if thread_count > settings.MAX_THREAD_COUNT:
101 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
99 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
102 old_threads = threads[-num_threads_to_delete:]
100 old_threads = threads[-num_threads_to_delete:]
103
101
104 for thread in old_threads:
102 for thread in old_threads:
105 self.delete_post(thread)
103 self.delete_post(thread)
106
104
105 def _bump_thread(self, thread_id):
106 thread = self.get(id=thread_id)
107
108 replies_count = len(self.get_thread(thread_id))
109 if replies_count <= settings.MAX_POSTS_PER_THREAD:
110 thread.last_edit_time = timezone.now()
111 thread.save()
112
107
113
108 class TagManager(models.Manager):
114 class TagManager(models.Manager):
109 def get_not_empty_tags(self):
115 def get_not_empty_tags(self):
110 all_tags = self.all().order_by('name')
116 all_tags = self.all().order_by('name')
111 tags = []
117 tags = []
112 for tag in all_tags:
118 for tag in all_tags:
113 if not tag.is_empty():
119 if not tag.is_empty():
114 tags.append(tag)
120 tags.append(tag)
115
121
116 return tags
122 return tags
117
123
118
124
119 class Tag(models.Model):
125 class Tag(models.Model):
120 """
126 """
121 A tag is a text node assigned to the post. The tag serves as a board
127 A tag is a text node assigned to the post. The tag serves as a board
122 section. There can be multiple tags for each message
128 section. There can be multiple tags for each message
123 """
129 """
124
130
125 objects = TagManager()
131 objects = TagManager()
126
132
127 name = models.CharField(max_length=100)
133 name = models.CharField(max_length=100)
128 # TODO Connect the tag to its posts to check the number of threads for
134 # TODO Connect the tag to its posts to check the number of threads for
129 # the tag.
135 # the tag.
130
136
131 def __unicode__(self):
137 def __unicode__(self):
132 return self.name
138 return self.name
133
139
134 def is_empty(self):
140 def is_empty(self):
135 return self.get_post_count() == 0
141 return self.get_post_count() == 0
136
142
137 def get_post_count(self):
143 def get_post_count(self):
138 posts_with_tag = Post.objects.get_threads(tag=self)
144 posts_with_tag = Post.objects.get_threads(tag=self)
139 return len(posts_with_tag)
145 return len(posts_with_tag)
140
146
141
147
142 class Post(models.Model):
148 class Post(models.Model):
143 """A post is a message."""
149 """A post is a message."""
144
150
145 objects = PostManager()
151 objects = PostManager()
146
152
147 title = models.CharField(max_length=50)
153 title = models.CharField(max_length=50)
148 pub_time = models.DateTimeField()
154 pub_time = models.DateTimeField()
149 text = models.TextField()
155 text = models.TextField()
150 image = thumbs.ImageWithThumbsField(upload_to=update_image_filename,
156 image = thumbs.ImageWithThumbsField(upload_to=update_image_filename,
151 blank=True, sizes=((200, 150),))
157 blank=True, sizes=((200, 150),))
152 poster_ip = models.IPAddressField()
158 poster_ip = models.IPAddressField()
153 poster_user_agent = models.TextField()
159 poster_user_agent = models.TextField()
154 parent = models.BigIntegerField()
160 parent = models.BigIntegerField()
155 tags = models.ManyToManyField(Tag)
161 tags = models.ManyToManyField(Tag)
156 last_edit_time = models.DateTimeField()
162 last_edit_time = models.DateTimeField()
157
163
158 def __unicode__(self):
164 def __unicode__(self):
159 return self.title + ' (' + self.text + ')'
165 return self.title + ' (' + self.text + ')'
160
166
161 def _get_replies(self):
167 def _get_replies(self):
162 return Post.objects.filter(parent=self.id)
168 return Post.objects.filter(parent=self.id)
163
169
164 def get_reply_count(self):
170 def get_reply_count(self):
165 return len(self._get_replies())
171 return len(self._get_replies())
166
172
167 def get_images_count(self):
173 def get_images_count(self):
168 images_count = 1 if self.image else 0
174 images_count = 1 if self.image else 0
169 for reply in self._get_replies():
175 for reply in self._get_replies():
170 if reply.image:
176 if reply.image:
171 images_count += 1
177 images_count += 1
172
178
173 return images_count
179 return images_count
174
180
175 def get_gets_count(self):
181 def get_gets_count(self):
176 gets_count = 1 if self.is_get() else 0
182 gets_count = 1 if self.is_get() else 0
177 for reply in self._get_replies():
183 for reply in self._get_replies():
178 if reply.is_get():
184 if reply.is_get():
179 gets_count += 1
185 gets_count += 1
180
186
181 return gets_count
187 return gets_count
182
188
183 def is_get(self):
189 def is_get(self):
184 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
190 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
185
191
186 # TODO Make a better algorithm for describing gets
192 # TODO Make a better algorithm for describing gets
187 return self.id == 1 or self.id % 10 == 0
193 return self.id == 1 or self.id % 10 == 0
188
194
189 def get_parsed_text(self):
195 def get_parsed_text(self):
190 text = self.text
196 text = self.text
191
197
192 # TODO Implement parsing
198 # TODO Implement parsing
193 # Make //text colored
199 # Make //text colored
194 # Make >>12 links to posts
200 # Make >>12 links to posts
195
201
196 return text
202 return text
197
203
198
204
199 class Admin(models.Model):
205 class Admin(models.Model):
200 """
206 """
201 Model for admin users
207 Model for admin users
202 """
208 """
203 name = models.CharField(max_length=100)
209 name = models.CharField(max_length=100)
204 password = models.CharField(max_length=100)
210 password = models.CharField(max_length=100)
205
211
206 def __unicode__(self):
212 def __unicode__(self):
207 return self.name + '/' + '*' * len(self.password)
213 return self.name + '/' + '*' * len(self.password)
208
214
@@ -1,177 +1,177 b''
1 # Django settings for neboard project.
1 # Django settings for neboard project.
2 import os
2 import os
3
3
4 DEBUG = True
4 DEBUG = True
5 TEMPLATE_DEBUG = DEBUG
5 TEMPLATE_DEBUG = DEBUG
6
6
7 ADMINS = (
7 ADMINS = (
8 # ('Your Name', 'your_email@example.com'),
8 # ('Your Name', 'your_email@example.com'),
9 ('admin', 'admin@example.com')
9 ('admin', 'admin@example.com')
10 )
10 )
11
11
12 MANAGERS = ADMINS
12 MANAGERS = ADMINS
13
13
14 DATABASES = {
14 DATABASES = {
15 'default': {
15 'default': {
16 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
16 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
17 'NAME': 'database.db', # Or path to database file if using sqlite3.
17 'NAME': 'database.db', # Or path to database file if using sqlite3.
18 'USER': '', # Not used with sqlite3.
18 'USER': '', # Not used with sqlite3.
19 'PASSWORD': '', # Not used with sqlite3.
19 'PASSWORD': '', # Not used with sqlite3.
20 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
20 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
21 'PORT': '', # Set to empty string for default. Not used with sqlite3.
21 'PORT': '', # Set to empty string for default. Not used with sqlite3.
22 }
22 }
23 }
23 }
24
24
25 # Local time zone for this installation. Choices can be found here:
25 # Local time zone for this installation. Choices can be found here:
26 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
26 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
27 # although not all choices may be available on all operating systems.
27 # although not all choices may be available on all operating systems.
28 # In a Windows environment this must be set to your system time zone.
28 # In a Windows environment this must be set to your system time zone.
29 TIME_ZONE = 'Europe/Kiev'
29 TIME_ZONE = 'Europe/Kiev'
30
30
31 # Language code for this installation. All choices can be found here:
31 # Language code for this installation. All choices can be found here:
32 # http://www.i18nguy.com/unicode/language-identifiers.html
32 # http://www.i18nguy.com/unicode/language-identifiers.html
33 LANGUAGE_CODE = 'ru'
33 LANGUAGE_CODE = 'ru'
34
34
35 SITE_ID = 1
35 SITE_ID = 1
36
36
37 # If you set this to False, Django will make some optimizations so as not
37 # If you set this to False, Django will make some optimizations so as not
38 # to load the internationalization machinery.
38 # to load the internationalization machinery.
39 USE_I18N = True
39 USE_I18N = True
40
40
41 # If you set this to False, Django will not format dates, numbers and
41 # If you set this to False, Django will not format dates, numbers and
42 # calendars according to the current locale.
42 # calendars according to the current locale.
43 USE_L10N = True
43 USE_L10N = True
44
44
45 # If you set this to False, Django will not use timezone-aware datetimes.
45 # If you set this to False, Django will not use timezone-aware datetimes.
46 USE_TZ = True
46 USE_TZ = True
47
47
48 # Absolute filesystem path to the directory that will hold user-uploaded files.
48 # Absolute filesystem path to the directory that will hold user-uploaded files.
49 # Example: "/home/media/media.lawrence.com/media/"
49 # Example: "/home/media/media.lawrence.com/media/"
50 MEDIA_ROOT = ''
50 MEDIA_ROOT = ''
51
51
52 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
52 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
53 # trailing slash.
53 # trailing slash.
54 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
54 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
55 MEDIA_URL = ''
55 MEDIA_URL = ''
56
56
57 # Absolute path to the directory static files should be collected to.
57 # Absolute path to the directory static files should be collected to.
58 # Don't put anything in this directory yourself; store your static files
58 # Don't put anything in this directory yourself; store your static files
59 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
59 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
60 # Example: "/home/media/media.lawrence.com/static/"
60 # Example: "/home/media/media.lawrence.com/static/"
61 STATIC_ROOT = ''
61 STATIC_ROOT = ''
62
62
63 # URL prefix for static files.
63 # URL prefix for static files.
64 # Example: "http://media.lawrence.com/static/"
64 # Example: "http://media.lawrence.com/static/"
65 STATIC_URL = '/static/'
65 STATIC_URL = '/static/'
66
66
67 # Additional locations of static files
67 # Additional locations of static files
68 # It is really a hack, put real paths, not related
68 # It is really a hack, put real paths, not related
69 STATICFILES_DIRS = (
69 STATICFILES_DIRS = (
70 os.path.dirname(__file__) + '/boards/static',
70 os.path.dirname(__file__) + '/boards/static',
71
71
72 # '/d/work/python/django/neboard/neboard/boards/static',
72 # '/d/work/python/django/neboard/neboard/boards/static',
73 # Put strings here, like "/home/html/static" or "C:/www/django/static".
73 # Put strings here, like "/home/html/static" or "C:/www/django/static".
74 # Always use forward slashes, even on Windows.
74 # Always use forward slashes, even on Windows.
75 # Don't forget to use absolute paths, not relative paths.
75 # Don't forget to use absolute paths, not relative paths.
76 )
76 )
77
77
78 # List of finder classes that know how to find static files in
78 # List of finder classes that know how to find static files in
79 # various locations.
79 # various locations.
80 STATICFILES_FINDERS = (
80 STATICFILES_FINDERS = (
81 'django.contrib.staticfiles.finders.FileSystemFinder',
81 'django.contrib.staticfiles.finders.FileSystemFinder',
82 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
82 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
83 # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
83 # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
84 )
84 )
85
85
86 # Make this unique, and don't share it with anybody.
86 # Make this unique, and don't share it with anybody.
87 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
87 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
88
88
89 # List of callables that know how to import templates from various sources.
89 # List of callables that know how to import templates from various sources.
90 TEMPLATE_LOADERS = (
90 TEMPLATE_LOADERS = (
91 'django.template.loaders.filesystem.Loader',
91 'django.template.loaders.filesystem.Loader',
92 'django.template.loaders.app_directories.Loader',
92 'django.template.loaders.app_directories.Loader',
93 # 'django.template.loaders.eggs.Loader',
93 # 'django.template.loaders.eggs.Loader',
94 )
94 )
95
95
96 TEMPLATE_CONTEXT_PROCESSORS = (
96 TEMPLATE_CONTEXT_PROCESSORS = (
97 'django.core.context_processors.media',
97 'django.core.context_processors.media',
98 'django.core.context_processors.static',
98 'django.core.context_processors.static',
99 'django.core.context_processors.request',
99 'django.core.context_processors.request',
100 'django.contrib.auth.context_processors.auth',
100 'django.contrib.auth.context_processors.auth',
101 )
101 )
102
102
103 MIDDLEWARE_CLASSES = (
103 MIDDLEWARE_CLASSES = (
104 'django.middleware.common.CommonMiddleware',
104 'django.middleware.common.CommonMiddleware',
105 'django.contrib.sessions.middleware.SessionMiddleware',
105 'django.contrib.sessions.middleware.SessionMiddleware',
106 # 'django.middleware.csrf.CsrfViewMiddleware',
106 # 'django.middleware.csrf.CsrfViewMiddleware',
107 'django.contrib.auth.middleware.AuthenticationMiddleware',
107 'django.contrib.auth.middleware.AuthenticationMiddleware',
108 'django.contrib.messages.middleware.MessageMiddleware',
108 'django.contrib.messages.middleware.MessageMiddleware',
109 # Uncomment the next line for simple clickjacking protection:
109 # Uncomment the next line for simple clickjacking protection:
110 # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
110 # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
111 )
111 )
112
112
113 ROOT_URLCONF = 'neboard.urls'
113 ROOT_URLCONF = 'neboard.urls'
114
114
115 # Python dotted path to the WSGI application used by Django's runserver.
115 # Python dotted path to the WSGI application used by Django's runserver.
116 WSGI_APPLICATION = 'neboard.wsgi.application'
116 WSGI_APPLICATION = 'neboard.wsgi.application'
117
117
118 TEMPLATE_DIRS = (
118 TEMPLATE_DIRS = (
119 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
119 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
120 # Always use forward slashes, even on Windows.
120 # Always use forward slashes, even on Windows.
121 # Don't forget to use absolute paths, not relative paths.
121 # Don't forget to use absolute paths, not relative paths.
122 'templates',
122 'templates',
123 )
123 )
124
124
125 INSTALLED_APPS = (
125 INSTALLED_APPS = (
126 'django.contrib.auth',
126 'django.contrib.auth',
127 'django.contrib.contenttypes',
127 'django.contrib.contenttypes',
128 'django.contrib.sessions',
128 'django.contrib.sessions',
129 'django.contrib.sites',
129 'django.contrib.sites',
130 'django.contrib.messages',
130 'django.contrib.messages',
131 'django.contrib.staticfiles',
131 'django.contrib.staticfiles',
132 # Uncomment the next line to enable the admin:
132 # Uncomment the next line to enable the admin:
133 'django.contrib.admin',
133 'django.contrib.admin',
134 # Uncomment the next line to enable admin documentation:
134 # Uncomment the next line to enable admin documentation:
135 # 'django.contrib.admindocs',
135 # 'django.contrib.admindocs',
136 'django.contrib.markup',
136 'django.contrib.markup',
137 'boards',
137 'boards',
138 )
138 )
139
139
140 # A sample logging configuration. The only tangible logging
140 # A sample logging configuration. The only tangible logging
141 # performed by this configuration is to send an email to
141 # performed by this configuration is to send an email to
142 # the site admins on every HTTP 500 error when DEBUG=False.
142 # the site admins on every HTTP 500 error when DEBUG=False.
143 # See http://docs.djangoproject.com/en/dev/topics/logging for
143 # See http://docs.djangoproject.com/en/dev/topics/logging for
144 # more details on how to customize your logging configuration.
144 # more details on how to customize your logging configuration.
145 LOGGING = {
145 LOGGING = {
146 'version': 1,
146 'version': 1,
147 'disable_existing_loggers': False,
147 'disable_existing_loggers': False,
148 'filters': {
148 'filters': {
149 'require_debug_false': {
149 'require_debug_false': {
150 '()': 'django.utils.log.RequireDebugFalse'
150 '()': 'django.utils.log.RequireDebugFalse'
151 }
151 }
152 },
152 },
153 'handlers': {
153 'handlers': {
154 'mail_admins': {
154 'mail_admins': {
155 'level': 'ERROR',
155 'level': 'ERROR',
156 'filters': ['require_debug_false'],
156 'filters': ['require_debug_false'],
157 'class': 'django.utils.log.AdminEmailHandler'
157 'class': 'django.utils.log.AdminEmailHandler'
158 }
158 }
159 },
159 },
160 'loggers': {
160 'loggers': {
161 'django.request': {
161 'django.request': {
162 'handlers': ['mail_admins'],
162 'handlers': ['mail_admins'],
163 'level': 'ERROR',
163 'level': 'ERROR',
164 'propagate': True,
164 'propagate': True,
165 },
165 },
166 }
166 }
167 }
167 }
168
168
169 # Custom imageboard settings
169 # Custom imageboard settings
170 MAX_POSTS_PER_THREAD = 100
170 MAX_POSTS_PER_THREAD = 500 # Thread bumplimit
171 MAX_THREAD_COUNT = 20
171 MAX_THREAD_COUNT = 20 # Old threads will be deleted to preserve this count
172 SITE_NAME = 'Neboard'
172 SITE_NAME = 'Neboard'
173
173
174 THEMES = [
174 THEMES = [
175 ('md', 'Mystic Dark'),
175 ('md', 'Mystic Dark'),
176 ('sw', 'Snow White') ]
176 ('sw', 'Snow White') ]
177 DEFAULT_THEME = 'md' No newline at end of file
177 DEFAULT_THEME = 'md'
@@ -1,74 +1,74 b''
1 {% extends "base.html" %}
1 {% extends "base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>Neboard</title>
7 <title>Neboard</title>
8 {% endblock %}
8 {% endblock %}
9
9
10 {% block content %}
10 {% block content %}
11
11
12 {% if posts %}
12 {% if posts %}
13 {% for post in posts %}
13 {% for post in posts %}
14 <a name="{{ post.id }}"></a>
14 <a name="{{ post.id }}"></a>
15 <div class="post">
15 <div class="post">
16 {% if post.image %}
16 {% if post.image %}
17 <div class="block">
17 <div class="block">
18 <a href="{{ post.image.url }}"><img
18 <a href="{{ post.image.url }}"><img
19 src="{{ post.image.url_200x150 }}" />
19 src="{{ post.image.url_200x150 }}" />
20 </a>
20 </a>
21 </div>
21 </div>
22 {% endif %}
22 {% endif %}
23 <div class="block">
23 <div class="block">
24 <span class="title">{{ post.title }}</span>
24 <span class="title">{{ post.title }}</span>
25 <a class="post_id" href="#{{ post.id }}">
25 <a class="post_id" href="#{{ post.id }}">
26 (#{{ post.id }})</a>
26 (#{{ post.id }})</a>
27 [{{ post.pub_time }}]
27 [{{ post.pub_time }}]
28 {{ post.get_parsed_text|markdown:"safe" }}
28 {{ post.get_parsed_text|markdown:"safe" }}
29 </div>
29 </div>
30 {% if post.tags.all %}
30 {% if post.tags.all %}
31 <div class="metadata">
31 <div class="metadata">
32 <span class="tags">{% trans 'Tags' %}:
32 <span class="tags">{% trans 'Tags' %}:
33 {% for tag in post.tags.all %}
33 {% for tag in post.tags.all %}
34 <a class="tag" href="{% url 'tag' tag.name %}">
34 <a class="tag" href="{% url 'tag' tag.name %}">
35 {{ tag.name }}</a>
35 {{ tag.name }}</a>
36 {% endfor %}
36 {% endfor %}
37 </span>
37 </span>
38 </div>
38 </div>
39 {% endif %}
39 {% endif %}
40 </div>
40 </div>
41 {% endfor %}
41 {% endfor %}
42 {% else %}
42 {% else %}
43 No threads found.
43 No threads found.
44 <hr />
44 <hr />
45 {% endif %}
45 {% endif %}
46
46
47 <form enctype="multipart/form-data" method="post">{% csrf_token %}
47 <form enctype="multipart/form-data" method="post">{% csrf_token %}
48 <div class="post-form-w">
48 <div class="post-form-w">
49 <div class="form-title">{% trans "Create new thread" %}</div>
49 <div class="form-title">{% trans "Reply to thread" %}</div>
50 <div class="post-form">
50 <div class="post-form">
51 <div class="form-row">
51 <div class="form-row">
52 <div class="form-label">{% trans 'Title' %}</div>
52 <div class="form-label">{% trans 'Title' %}</div>
53 <div class="form-input">{{ form.title }}</div>
53 <div class="form-input">{{ form.title }}</div>
54 </div>
54 </div>
55 <div class="form-row">
55 <div class="form-row">
56 <div class="form-label">{% trans 'Text' %}</div>
56 <div class="form-label">{% trans 'Text' %}</div>
57 <div class="form-input">{{ form.text }}</div>
57 <div class="form-input">{{ form.text }}</div>
58 </div>
58 </div>
59 <div class="form-row">
59 <div class="form-row">
60 <div class="form-label">{% trans 'Image' %}</div>
60 <div class="form-label">{% trans 'Image' %}</div>
61 <div class="form-input">{{ form.image }}</div>
61 <div class="form-input">{{ form.image }}</div>
62 </div>
62 </div>
63 </div>
63 </div>
64 <div class="form-submit"><input type="submit"
64 <div class="form-submit"><input type="submit"
65 value="{% trans "Post" %}"/></div>
65 value="{% trans "Post" %}"/></div>
66 <div>Use <a
66 <div>Use <a
67 href="http://daringfireball.net/projects/markdown/basics">
67 href="http://daringfireball.net/projects/markdown/basics">
68 markdown</a> syntax for posting.</div>
68 markdown</a> syntax for posting.</div>
69 <div>Example: *<i>italic</i>*, **<b>bold</b>**</div>
69 <div>Example: *<i>italic</i>*, **<b>bold</b>**</div>
70 <div>Insert quotes with "&gt;"</div>
70 <div>Insert quotes with "&gt;"</div>
71 </div>
71 </div>
72 </form>
72 </form>
73
73
74 {% endblock %} No newline at end of file
74 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now