Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit 7462abd2c3dea5730fabb8453b9d2f12828829b4 0 parents
@robmoggach authored
9 .gitattributes
@@ -0,0 +1,9 @@
+* eol=lf
+*.png binary
+*.jpg binary
+*.gif binary
+*.jar binary
+*.exe binary
+*.eot binary
+*.ttf binary
+*.pdf binary
25 .gitignore
@@ -0,0 +1,25 @@
+*~
+*.db
+*.orig
+*.pyc
+*.pyo
+*.rej
+.installed.cfg
+eggs
+log
+parts
+tmp
+.DS_Store
+.hg
+src/*.egg-info
+dist
+pip-log.txt
+.Python
+lib
+.mr.developer.cfg
+src
+passwords.py
+settings_local.py
+build
+apache2
+*.py.swp
2  .hgignore
@@ -0,0 +1,2 @@
+glob:*.py[cow]
+glob:.emacs*
23 LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2012, Robert Moggach
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
4 MANIFEST.in
@@ -0,0 +1,4 @@
+exclude .hgignore
+include CHANGELOG LICENSE README.rst
+recursive-include mezzanine-careers *
+
34 README.rst
@@ -0,0 +1,34 @@
+==================
+ Mezzanine Careers
+==================
+
+Mezzanine Careers is a simple Job Posting application built for the `Django`_ based CMS `Mezzanine`_.
+
+Features
+========
+
+* Create a list of job posting with delayed and expiring durations
+* Provide basic templates to get started
+* Seamlessly integrate with the Mezzanine CMS
+
+Installation and Usage
+======================
+
+Install the app by using `pip`_::
+
+ $ pip install mezzanine-careers
+
+Add ``careers`` to ``INSTALLED_APPS`` and synchronize/migrate the database.
+
+Go to the admin site and create new polls in the ``Pages`` section
+
+Development
+===========
+
+New features and fixes are welcome. Better if it comes with a patch or pull request. You can find the latest development sources at `GitHub`_.
+Issues are tracked at the GitHub project page.
+
+.. _`Mezzanine`: http://mezzanine.jupo.org/
+.. _`Django`: http://djangoproject.com/
+.. _`pip`: http://www.pip-installer.org/
+.. _`GitHub`: https://github.com/mogga/mezzanine-careers
1  careers/__init__.py
@@ -0,0 +1 @@
+__version__ = 0.1
28 careers/admin.py
@@ -0,0 +1,28 @@
+
+from copy import deepcopy
+
+from django.contrib import admin
+
+from careers.models import JobPost
+from mezzanine.conf import settings
+from mezzanine.core.admin import DisplayableAdmin
+
+
+jobpost_fieldsets = deepcopy(DisplayableAdmin.fieldsets)
+jobpost_fieldsets[0][1]["fields"].extend(["content",])
+jobpost_list_display = ["title", "status", "admin_link"]
+
+
+class JobPostAdmin(DisplayableAdmin):
+ """
+ Admin class for job posts.
+ """
+
+ fieldsets = jobpost_fieldsets
+ list_display = jobpost_list_display
+
+ def save_form(self, request, form, change):
+ return DisplayableAdmin.save_form(self, request, form, change)
+
+
+admin.site.register(JobPost, JobPostAdmin)
19 careers/defaults.py
@@ -0,0 +1,19 @@
+from django.utils.translation import ugettext_lazy as _
+
+from mezzanine.conf import register_setting
+
+
+register_setting(
+ name="jobpostS_PER_PAGE",
+ label=_("Job Posts per page"),
+ description=_("Number of job posts shown on a career listing page."),
+ editable=True,
+ default=7,
+)
+
+register_setting(
+ name="jobpost_SLUG",
+ description=_("Slug of the page object for the careers app."),
+ editable=False,
+ default="jobpost",
+)
25 careers/forms.py
@@ -0,0 +1,25 @@
+from django import forms
+
+from careers.models import JobPost
+
+
+hidden_field_defaults = ("status", "gen_description")
+
+
+class JobPostForm(forms.ModelForm):
+ """
+ Model form for ``JobPost`` that provides the quick job post panel in the
+ admin dashboard.
+ """
+
+ class Meta:
+ model = JobPost
+ fields = ("title", "content") + hidden_field_defaults
+
+ def __init__(self):
+ initial = {}
+ for field in hidden_field_defaults:
+ initial[field] = JobPost._meta.get_field(field).default
+ super(JobPostForm, self).__init__(initial=initial)
+ for field in hidden_field_defaults:
+ self.fields[field].widget = forms.HiddenInput()
84 careers/migrations/0001_initial.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'JobPost'
+ db.create_table('careers_jobpost', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('keywords_string', self.gf('django.db.models.fields.CharField')(max_length=500, blank=True)),
+ ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=500)),
+ ('slug', self.gf('django.db.models.fields.CharField')(max_length=2000, null=True, blank=True)),
+ ('_meta_title', self.gf('django.db.models.fields.CharField')(max_length=500, null=True, blank=True)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('gen_description', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('status', self.gf('django.db.models.fields.IntegerField')(default=2)),
+ ('publish_date', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('expiry_date', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('short_url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)),
+ ('content', self.gf('mezzanine.core.fields.RichTextField')()),
+ ('keywords', self.gf('mezzanine.generic.fields.KeywordsField')(object_id_field='object_pk', to=orm['generic.AssignedKeyword'], frozen_by_south=True)),
+ ))
+ db.send_create_signal('careers', ['JobPost'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'JobPost'
+ db.delete_table('careers_jobpost')
+
+
+ models = {
+ 'careers.jobpost': {
+ 'Meta': {'ordering': "('-publish_date',)", 'object_name': 'JobPost'},
+ '_meta_title': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
+ 'content': ('mezzanine.core.fields.RichTextField', [], {}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'gen_description': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'keywords': ('mezzanine.generic.fields.KeywordsField', [], {'object_id_field': "'object_pk'", 'to': "orm['generic.AssignedKeyword']", 'frozen_by_south': 'True'}),
+ 'keywords_string': ('django.db.models.fields.CharField', [], {'max_length': '500', 'blank': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'short_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '500'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'generic.assignedkeyword': {
+ 'Meta': {'ordering': "('_order',)", 'object_name': 'AssignedKeyword'},
+ '_order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'keyword': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'assignments'", 'to': "orm['generic.Keyword']"}),
+ 'object_pk': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'generic.keyword': {
+ 'Meta': {'object_name': 'Keyword'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '500'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['careers']
0  careers/migrations/__init__.py
No changes.
26 careers/models.py
@@ -0,0 +1,26 @@
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from mezzanine.conf import settings
+from mezzanine.core.models import Displayable, RichText, Ownable
+
+
+class JobPost(Displayable, RichText):
+ """
+ A career job posting
+ """
+
+ class Meta:
+ verbose_name = _("Job Post")
+ verbose_name_plural = _("Job Posts")
+ ordering = ("-publish_date",)
+
+ @models.permalink
+ def get_absolute_url(self):
+ url_name = "jobpost_detail"
+ kwargs = {"slug": self.slug}
+ return (url_name, (), kwargs)
+
+ def keyword_list(self):
+ return getattr(self, "_keywords", self.keywords.all())
+
33 careers/templates/admin/includes/quick_jobpost.html
@@ -0,0 +1,33 @@
+{% load i18n %}
+
+{% if perms.careers.add_jobpost and perms.careers.change_jobpost %}
+<script>
+// Format newlines for HTML in the quick blog form, since the content
+// is HTML but the form field is plain text.
+$(function() {
+ $('#quick-job-post-form').submit(function() {
+ var field = $('#quick-job-post-form #id_content');
+ var value = field.attr('value').split('\n\n').join('</p><p>');
+ value = '<p>' + value.split('\n').join('<br>') + '</p>';
+ field.attr('value', value);
+ return true;
+ });
+});
+</script>
+<div class="module">
+ <h2>{% trans "Quick Job Post" %}</h2>
+ <form method="post" id="quick-job-post-form" action="{% url admin:careers_jobpost_add %}">
+ {% csrf_token %}
+ <table id="quick-job-post">
+ {{ form.as_table }}
+ <tr>
+ <td>&nbsp;</td>
+ <td style="width:100%;text-align:right;">
+ <input type="submit" class="default"
+ value="{% trans "Save Draft" %}" />
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+{% endif %}
42 careers/templates/careers/includes/filter_panel.html
@@ -0,0 +1,42 @@
+{% load career_tags keyword_tags i18n %}
+
+{% jobpost_recent_posts 5 as recent_posts %}
+{% if recent_posts %}
+<h3>{% trans "Recent Posts" %}</h3>
+<ul class="unstyled recent-posts">
+{% for recent_post in recent_posts %}
+<li><a href="{{ recent_post.get_absolute_url }}"
+ >{{ recent_post.title }}</a></li>
+{% endfor %}
+</ul>
+{% endif %}
+
+{% jobpost_months as months %}
+{% if months %}
+<h3>{% trans "Archive" %}</h3>
+{% for month in months %}
+ {% ifchanged month.date.year %}
+ {% if not forloop.first %}</ul>{% endif %}
+ <h6>{{ month.date.year }}</h6><ul class="unstyled">
+ {% endifchanged %}
+ <li><a href="{% url jobpost_list_month year=month.date.year month=month.date.month %}"
+ >{{ month.date|date:"F" }}</a> ({{ month.post_count }})</li>
+{% endfor %}
+</ul>
+{% endif %}
+
+{% keywords_for careers.jobpost as tags %}
+{% if tags %}
+<h3>{% trans "Tags" %}</h3>
+<ul class="unstyled tags">
+{% for tag in tags %}
+<li>
+ <a href="{% url jobpost_list_tag tag.slug %}"
+ class="tag-weight-{{ tag.weight }}">{{ tag }}</a>
+ ({{ tag.item_count }})
+</li>
+{% endfor %}
+</ul>
+{% endif %}
+
+
51 careers/templates/careers/jobpost_detail.html
@@ -0,0 +1,51 @@
+{% extends "careers/jobpost_list.html" %}
+{% load mezzanine_tags keyword_tags i18n %}
+
+{% block meta_title %}{{ jobpost.meta_title }}{% endblock %}
+
+{% block meta_keywords %}{% metablock %}
+{% keywords_for jobpost as tags %}
+{% for tag in tags %}{% if not forloop.first %}, {% endif %}{{ tag }}{% endfor %}
+{% endmetablock %}{% endblock %}
+
+{% block meta_description %}{% metablock %}
+{{ jobpost.description }}
+{% endmetablock %}{% endblock %}
+
+{% block title %}
+{% editable jobpost.title %}{{ jobpost.title }}{% endeditable %}
+{% endblock %}
+
+{% block breadcrumb_menu %}
+{{ block.super }}
+<li class="active">{{ jobpost.title }}</li>
+{% endblock %}
+
+{% block main %}
+
+<h6>
+ {% trans "Posted" %} {{ jobpost.publish_date|timesince }} {% trans "ago" %}.
+</h6>
+
+{% editable jobpost.content %}
+{{ jobpost.content|richtext_filter|safe }}
+{% endeditable %}
+
+{% keywords_for jobpost as tags %}
+{% if tags %}
+{% spaceless %}
+<ul class="unstyled tags">
+ <li>{% trans "Tags" %}:</li>
+ {% for tag in tags %}
+ <li><a href="{% url jobpost_list_tag tag.slug %}">{{ tag }}</a></li>
+ {% endfor %}
+</ul>
+{% endspaceless %}
+{% endif %}
+
+
+{% set_short_url_for jobpost %}
+<a class="btn small primary share-twitter" target="_blank" href="http://twitter.com/home?status={{ jobpost.short_url|urlencode }}%20{{ jobpost.title|urlencode }}">{% trans "Share on Twitter" %}</a>
+<a class="btn small primary share-facebook" target="_blank" href="http://facebook.com/sharer.php?u={{ request.build_absolute_uri }}&amp;t={{ jobpost.title|urlencode }}">{% trans "Share on Facebook" %}</a>
+
+{% endblock %}
102 careers/templates/careers/jobpost_list.html
@@ -0,0 +1,102 @@
+{% extends "base.html" %}
+{% load i18n mezzanine_tags career_tags keyword_tags %}
+
+{% block meta_title %}{% if page %}{{ page.richtextpage.meta_title }}{% else %}{% trans "Careers" %}{% endif %}{% endblock %}
+
+{% block meta_keywords %}{% metablock %}
+{% keywords_for page as keywords %}
+{% for keyword in keywords %}
+ {% if not forloop.first %}, {% endif %}
+ {{ keyword }}
+{% endfor %}
+{% endmetablock %}{% endblock %}
+
+{% block meta_description %}{% metablock %}
+{{ page.description }}
+{% endmetablock %}{% endblock %}
+
+{% block title %}
+{% if page %}
+{% editable page.title %}{{ page.title }}{% endeditable %}
+{% else %}
+{% trans "Careers" %}
+{% endif %}
+{% endblock %}
+
+{% block breadcrumb_menu %}
+{{ block.super }}
+{% if tag or category or year or month or author %}
+<li>{% spaceless %}
+{% if tag %}
+ {% trans "Tag:" %} {{ tag }}
+{% else %}{% if category %}
+ {% trans "Category:" %} {{ category }}
+{% else %}{% if year or month %}
+ {% if month %}{{ month }}, {% endif %}{{ year }}
+{% else %}{% if author %}
+ {% trans "Author:" %} {{ author.get_full_name|default:author.username }}
+{% endif %}{% endif %}{% endif %}{% endif %}
+{% endspaceless %}
+</li>
+{% endif %}
+{% endblock %}
+
+{% block main %}
+
+{% if tag or category or year or month or author %}
+ <p>
+ {% if tag %}
+ {% trans "Viewing posts tagged" %} {{ tag }}
+ {% else %}{% if category %}
+ {% trans "Viewing posts for the category" %} {{ category }}
+ {% else %}{% if year or month %}
+ {% trans "Viewing posts from" %} {% if month %}{{ month }}, {% endif %}
+ {{ year }}
+ {% else %}{% if author %}
+ {% trans "Viewing posts by" %}
+ {{ author.get_full_name|default:author.username }}
+ {% endif %}{% endif %}{% endif %}{% endif %}
+ </p>
+{% else %}
+ {% if page %}
+ {% editable page.richtextpage.content %}
+ {{ page.richtextpage.content|safe }}
+ {% endeditable %}
+ {% endif %}
+{% endif %}
+
+{% for jobpost in jobposts.object_list %}
+{% editable jobpost.title jobpost.publish_date %}
+<h2>
+ <a href="{{ jobpost.get_absolute_url }}">{{ jobpost.title }}</a>
+</h2>
+<h6>
+ {% trans "Posted" %} {{ jobpost.publish_date|timesince }} {% trans "ago" %}.
+</h6>
+{% endeditable %}
+
+{% editable jobpost.content %}
+{{ jobpost.description_from_content|safe }}
+{% endeditable %}
+
+<p class="job-post-list-detail">
+ {% if jobpost.keyword_list %}
+ {% trans "Tags" %}:
+ {% spaceless %}
+ {% for tag in jobpost.keyword_list %}
+ <a href="{% url jobpost_list_tag tag.slug %}" class="tag">{{ tag }}</a>
+ {% endfor %}
+ {% endspaceless %}
+ <br>
+ {% endif %}
+ <a href="{{ jobpost.get_absolute_url }}">{% trans "read more" %}</a>
+</p>
+{% endfor %}
+
+{% pagination_for jobposts %}
+
+{% endblock %}
+
+{% block right_panel %}
+{% include "careers/includes/filter_panel.html" %}
+{% endblock %}
0  careers/templatetags/__init__.py
No changes.
44 careers/templatetags/career_tags.py
@@ -0,0 +1,44 @@
+from datetime import datetime
+
+from django.contrib.auth.models import User
+from django.db.models import Count
+
+from dashing.careers.forms import CareerForm
+from dashing.careers.models import Career
+from mezzanine import template
+
+
+register = template.Library()
+
+
+@register.as_tag
+def career_months(*args):
+ """
+ Put a list of dates for careers into the template context.
+ """
+ dates = Career.objects.published().values_list("publish_date", flat=True)
+ date_dicts = [{"date": datetime(d.year, d.month, 1)} for d in dates]
+ month_dicts = []
+ for date_dict in date_dicts:
+ if date_dict not in month_dicts:
+ month_dicts.append(date_dict)
+ for i, date_dict in enumerate(month_dicts):
+ month_dicts[i]["post_count"] = date_dicts.count(date_dict)
+ return month_dicts
+
+
+@register.as_tag
+def career_recent_posts(limit=5):
+ """
+ Put a list of recently published careers into the template context.
+ """
+ return list(Career.objects.published()[:limit])
+
+
+@register.inclusion_tag("admin/includes/quick_career.html", takes_context=True)
+def quick_career(context):
+ """
+ Admin dashboard tag for the quick career form.
+ """
+ context["form"] = CareerForm()
+ return context
29 careers/urls.py
@@ -0,0 +1,29 @@
+from django.conf.urls.defaults import patterns, url
+
+# Job Post patterns.
+urlpatterns = patterns("careers.views",
+
+ url("^tag/(?P<tag>.*)/$",
+ "jobpost_list",
+ name="jobpost_list_tag"),
+
+ url("^archive/(?P<year>\d{4})/(?P<month>\d{1,2})/$",
+ "jobpost_list",
+ name="jobpost_list_month"),
+
+ url("^archive/(?P<year>.*)/$",
+ "jobpost_list",
+ name="jobpost_list_year"),
+
+ url("^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>.*)/$",
+ "jobpost_detail",
+ name="jobpost_detail_date"),
+
+ url("^(?P<slug>.*)/$",
+ "jobpost_detail",
+ name="jobpost_detail"),
+
+ url("^$",
+ "jobpost_list",
+ name="jobpost_list"),
+)
69 careers/views.py
@@ -0,0 +1,69 @@
+from calendar import month_name
+from django.shortcuts import get_object_or_404
+from collections import defaultdict
+from django.contrib.contenttypes.models import ContentType
+from django import VERSION
+
+from careers.models import JobPost
+from mezzanine.conf import settings
+from mezzanine.generic.models import AssignedKeyword, Keyword
+from mezzanine.utils.views import render, paginate
+
+
+def jobpost_list(request, tag=None, year=None, month=None, template="careers/jobpost_list.html"):
+ """
+ Display a list of job posts that are filtered by year, month.
+ """
+ settings.use_editable()
+ templates = []
+ jobposts = JobPost.objects.published()
+ if tag is not None:
+ tag = get_object_or_404(Keyword, slug=tag)
+ jobposts = jobposts.filter(keywords__in=tag.assignments.all())
+ if year is not None:
+ jobposts = jobposts.filter(publish_date__year=year)
+ if month is not None:
+ jobposts = jobposts.filter(publish_date__month=month)
+ month = month_name[int(month)]
+ # We want to iterate keywords and categories for each blog post
+ # without triggering "num posts x 2" queries.
+ #
+ # For Django 1.3 we create dicts mapping blog post IDs to lists of
+ # categories and keywords, and assign these to attributes on each
+ # blog post. The Blog model then uses accessor methods to retrieve
+ # these attributes when assigned, which will fall back to the real
+ # related managers for Django 1.4 and higher, which will already
+ # have their data retrieved via prefetch_related.
+
+ jobposts = jobposts.select_related("user")
+ if VERSION >= (1, 4):
+ jobposts = jobposts.prefetch_related("keywords__keyword")
+ else:
+ if jobposts:
+ ids = ",".join([str(p.id) for p in jobposts])
+ keywords = defaultdict(list)
+ jobpost_type = ContentType.objects.get(app_label="careers", model="jobpost")
+ assigned = AssignedKeyword.objects.filter(jobpost__in=jobposts, content_type=jobpost_type).select_related("keyword")
+ for a in assigned:
+ keywords[a.object_pk].append(a.keyword)
+ for i, post in enumerate(jobposts):
+ setattr(jobposts[i], "_keywords", keywords[post.id])
+ jobposts = paginate(jobposts, request.GET.get("page", 1),
+ settings.CAREERS_PER_PAGE,
+ settings.MAX_PAGING_LINKS)
+ context = {"jobposts": jobposts, "year": year, "month": month, "tag": tag}
+ templates.append(template)
+ return render(request, templates, context)
+
+
+def jobpost_detail(request, slug, year=None, month=None, day=None, template="careers/jobpost_detail.html"):
+ """. Custom templates are checked for using the name
+ ``careers/jobpost_detail_XXX.html`` where ``XXX`` is the job
+ posts's slug.
+ """
+ jobposts = JobPost.objects.published()
+ jobpost = get_object_or_404(jobposts, slug=slug)
+ context = {"jobpost": jobpost, "editable_obj": jobpost}
+ templates = [u"careers/jobpost_detail_%s.html" % unicode(slug), template]
+ return render(request, templates, context)
+
36 setup.py
@@ -0,0 +1,36 @@
+from setuptools import setup, find_packages
+import sys, os
+
+sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
+
+import careers
+
+setup(
+ name='Mezzanine Careers',
+ version=careers.__version__,
+ url='https://github.com/mogga/mezzanine_polls',
+ author='Robert Moggach',
+ author_email='rob@dashing.tv',
+ license='BSD',
+ description='Job Posting application for the Mezzanine CMS',
+ long_description=open('README.rst').read(),
+ keywords='django, mezzanine, careers',
+ packages=find_packages(),
+ setup_requires=('setuptools'),
+ install_requires=('setuptools', 'mezzanine'),
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI',
+ 'Topic :: Software Development :: Libraries :: Application Frameworks',
+ 'Topic :: Software Development :: Libraries :: Python Modules',],
+ zip_safe=False,
+ include_package_data=True,
+)
Please sign in to comment.
Something went wrong with that request. Please try again.