templates_distros_modulesdir = $(templates_distrosdir)/modules
+dist_templates_distros_releases_DATA = \
+ src/templates/distros/releases/index.html \
+ src/templates/distros/releases/show.html
+
+templates_distros_releasesdir = $(templates_distrosdir)/releases
+
+dist_templates_distros_releases_modules_DATA = \
+ src/templates/distros/releases/modules/list.html
+
+templates_distros_releases_modulesdir = $(templates_distros_releasesdir)/modules
+
dist_templates_monitorings_DATA = \
src/templates/monitorings/delete.html \
src/templates/monitorings/edit.html \
import logging
from . import base
+from . import misc
from .decorators import *
# Setup logging
""", name, distro_id, version_id,
)
+ @lazy_property
+ def releases(self):
+ """
+ Releases
+ """
+ return Releases(self.backend)
+
class Distribution(base.DataObject):
table = "distributions"
return builds
+ # Sources
+
@lazy_property
def sources(self):
sources = self.backend.sources._get_sources("""
)
return list(sources)
+
+ # Releases
+
+ def get_releases(self, limit=None, offset=None):
+ releases = self.backend.distros.releases._get_releases("""
+ SELECT
+ *
+ FROM
+ releases
+ WHERE
+ distro_id = %s
+ AND
+ deleted_at IS NULL
+ ORDER BY
+ created_at DESC
+ LIMIT
+ %s
+ OFFSET
+ %s
+ """, self.id, limit, offset,
+
+ # Populate cache
+ distro=self,
+ )
+
+ return list(releases)
+
+ def get_release(self, slug):
+ return self.backend.distros.releases._get_release("""
+ SELECT
+ *
+ FROM
+ releases
+ WHERE
+ distro_id = %s
+ AND
+ slug = %s
+ AND
+ deleted_at IS NULL
+ """, self.id, slug,
+ )
+
+
+class Releases(base.Object):
+ def _get_releases(self, query, *args, **kwargs):
+ return self.db.fetch_many(Release, query, *args, **kwargs)
+
+ def _get_release(self, query, *args, **kwargs):
+ return self.db.fetch_one(Release, query, *args, **kwargs)
+
+ def get_by_id(self, id):
+ return self._get_release("""
+ SELECT
+ *
+ FROM
+ releases
+ WHERE
+ id = %s
+ """, id,
+ )
+
+ def create(self, distro, repo, name, user, prerelease=False):
+ """
+ Creates a new release
+ """
+ # Create a slug
+ slug = misc.normalize(name)
+
+ release = self._get_release("""
+ INSERT INTO
+ releases
+ (
+ distro_id,
+ repo_id,
+ name,
+ slug,
+ created_by,
+ prerelease
+ )
+ VALUES
+ (
+ %s, %s, %s, %s, %s, %s
+ )
+ RETURNING *
+ """, distro, repo, name, slug, user, prerelease,
+
+ # Populate cache
+ distro=distro, repo=repo,
+ )
+
+ # XXX create image jobs
+
+ return release
+
+ # Images
+
+ @lazy_property
+ def images(self):
+ return Images(self.backend)
+
+
+class Release(base.DataObject):
+ table = "releases"
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.name)
+
+ def __str__(self):
+ return self.name
+
+ # Distro
+
+ @lazy_property
+ def distro(self):
+ return self.backend.distros.get_by_id(self.data.distro_id)
+
+ # Repo
+
+ @lazy_property
+ def repo(self):
+ return self.backend.repos.get_by_id(self.data.repo_id)
+
+ # Name
+
+ @property
+ def name(self):
+ return self.data.name
+
+ # Slug
+
+ @property
+ def slug(self):
+ return self.data.slug
+
+ # Created At
+
+ @property
+ def created_at(self):
+ return self.data.created_at
+
+ # Created By
+
+ @lazy_property
+ def created_by(self):
+ return self.backend.users.get_by_id(self.data.created_by)
+
+ # Deleted At
+
+ @property
+ def deleted_at(self):
+ return self.data.deleted_at
+
+ # Deleted By
+
+ @lazy_property
+ def deleted_by(self):
+ return self.backend.users.get_by_id(self.data.deleted_by)
+
+ # Pre-Release
+
+ @property
+ def prerelease(self):
+ return self.data.prerelease
+
+ # Announcement
+
+ def get_announcement(self):
+ return self.data.announcement
+
+ def set_announcement(self, text):
+ self._set_attribute("announcement", text)
+
+ announcement = property(get_announcement, set_announcement)
+
+ # URL
+
+ @property
+ def url(self):
+ return "/distros/%s/releases/%s" % (self.distro.slug, self.slug)
+
+ # Release
+
+ def is_released(self):
+ if self.released_at:
+ return True
+
+ return False
+
+ @property
+ def released_at(self):
+ return self.data.released_at
+
+ async def release(self, when=None):
+ """
+ Called to prepare releasing the release
+ """
+ if when:
+ self._set_attribute("released_at", when)
+ else:
+ self._set_attribute_now("released_at")
+
+ # XXX TODO
+
+ # Images
+
+ @lazy_property
+ def images(self):
+ images = self.backend.distros.releases.images._get_images("""
+ SELECT
+ *
+ FROM
+ release_images
+ WHERE
+ release_id = %s
+ AND
+ deleted_at IS NULL
+ """, self.id,
+
+ # Populate cache
+ release=self,
+ )
+
+ # Return grouped by architecture
+ return misc.group(images, lambda image: image.arch)
+
+
+class Images(base.Object):
+ def _get_images(self, query, *args, **kwargs):
+ return self.db.fetch_many(Image, query, *args, **kwargs)
+
+ def _get_image(self, query, *args, **kwargs):
+ return self.db.fetch_one(Image, query, *args, **kwargs)
+
+
+class Image(base.DataObject):
+ table = "release_images"
+
+ @lazy_property
+ def release(self):
+ return self.backend.distros.releases.get_by_id(self.data.release_id)
+
+ # Arch
+
+ @property
+ def arch(self):
+ return self.data.arch
ORDER BY (pg_relation_size((c.oid)::regclass)) DESC;
+--
+-- Name: release_images; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.release_images (
+ id integer NOT NULL,
+ release_id integer NOT NULL,
+ type_id integer NOT NULL,
+ arch text NOT NULL,
+ filename text NOT NULL,
+ path text
+);
+
+
+--
+-- Name: release_images_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.release_images_id_seq
+ AS integer
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: release_images_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.release_images_id_seq OWNED BY public.release_images.id;
+
+
--
-- Name: release_monitoring_releases; Type: TABLE; Schema: public; Owner: -
--
ALTER SEQUENCE public.release_monitorings_id_seq OWNED BY public.release_monitorings.id;
+--
+-- Name: releases; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.releases (
+ id integer NOT NULL,
+ distro_id integer NOT NULL,
+ repo_id integer NOT NULL,
+ name text NOT NULL,
+ slug text NOT NULL,
+ created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ created_by integer NOT NULL,
+ deleted_at timestamp without time zone,
+ deleted_by integer,
+ released_at timestamp without time zone,
+ prerelease boolean DEFAULT false NOT NULL,
+ announcement text
+);
+
+
+--
+-- Name: releases_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.releases_id_seq
+ AS integer
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.releases_id_seq OWNED BY public.releases.id;
+
+
--
-- Name: repo_builds; Type: TABLE; Schema: public; Owner: -
--
ALTER TABLE ONLY public.packages ALTER COLUMN id SET DEFAULT nextval('public.packages_id_seq'::regclass);
+--
+-- Name: release_images id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images ALTER COLUMN id SET DEFAULT nextval('public.release_images_id_seq'::regclass);
+
+
--
-- Name: release_monitoring_releases id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.release_monitorings ALTER COLUMN id SET DEFAULT nextval('public.release_monitorings_id_seq'::regclass);
+--
+-- Name: releases id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases ALTER COLUMN id SET DEFAULT nextval('public.releases_id_seq'::regclass);
+
+
--
-- Name: repositories id; Type: DEFAULT; Schema: public; Owner: -
--
ADD CONSTRAINT packages_pkey PRIMARY KEY (id);
+--
+-- Name: release_images release_images_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images
+ ADD CONSTRAINT release_images_pkey PRIMARY KEY (id);
+
+
--
-- Name: release_monitoring_releases release_monitoring_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ADD CONSTRAINT release_monitorings_pkey PRIMARY KEY (id);
+--
+-- Name: releases releases_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+ ADD CONSTRAINT releases_pkey PRIMARY KEY (id);
+
+
--
-- Name: repositories repositories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
CREATE UNIQUE INDEX release_monitorings_unique ON public.release_monitorings USING btree (distro_id, name) WHERE (deleted_at IS NULL);
+--
+-- Name: releases_distro_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX releases_distro_id ON public.releases USING btree (distro_id) WHERE (deleted_at IS NULL);
+
+
+--
+-- Name: releases_unique; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX releases_unique ON public.releases USING btree (slug) WHERE (deleted_at IS NULL);
+
+
--
-- Name: repo_builds_build_id; Type: INDEX; Schema: public; Owner: -
--
ADD CONSTRAINT packages_distro_id FOREIGN KEY (distro_id) REFERENCES public.distributions(id);
+--
+-- Name: release_images release_images_release_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images
+ ADD CONSTRAINT release_images_release_id FOREIGN KEY (release_id) REFERENCES public.releases(id);
+
+
+--
+-- Name: release_images release_images_type_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images
+ ADD CONSTRAINT release_images_type_id FOREIGN KEY (type_id) REFERENCES public.images_types(id);
+
+
--
-- Name: release_monitoring_releases release_monitoring_releases_build_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ADD CONSTRAINT release_monitorings_distro_id FOREIGN KEY (distro_id) REFERENCES public.distributions(id);
+--
+-- Name: releases releases_created_by; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+ ADD CONSTRAINT releases_created_by FOREIGN KEY (created_by) REFERENCES public.users(id);
+
+
+--
+-- Name: releases releases_deleted_by; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+ ADD CONSTRAINT releases_deleted_by FOREIGN KEY (deleted_by) REFERENCES public.users(id);
+
+
+--
+-- Name: releases releases_distro_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+ ADD CONSTRAINT releases_distro_id FOREIGN KEY (distro_id) REFERENCES public.distributions(id);
+
+
+--
+-- Name: releases releases_repo_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+ ADD CONSTRAINT releases_repo_id FOREIGN KEY (repo_id) REFERENCES public.repositories(id);
+
+
--
-- Name: repo_builds repo_builds_added_by; Type: FK CONSTRAINT; Schema: public; Owner: -
--
--- /dev/null
+{% extends "../../base.html" %}
+
+{% block title %}{{ distro }} - {{ _("Releases") }}{% end block %}
+
+{% block body %}
+ <section class="hero is-light">
+ <div class="hero-body">
+ <div class="container">
+ <nav class="breadcrumb" aria-label="breadcrumbs">
+ <ul>
+ <li>
+ <a href="/distros">{{ _("Distributions") }}</a>
+ </li>
+ <li>
+ <a href="/distros/{{ distro.slug }}">
+ {{ distro }}
+ </a>
+ </li>
+ <li class="is-active">
+ <a href="#" aria-current="page">{{ _("Releases") }}</a>
+ </li>
+ </ul>
+ </nav>
+
+ <h1 class="title">{{ _("Releases") }}</h1>
+ <h4 class="subtitle is-4">{{ distro }}</h4>
+ </div>
+ </div>
+ </section>
+
+ <section class="section">
+ <div class="container">
+ {% module ReleasesList(releases) %}
+ </div>
+ </section>
+{% end block %}
--- /dev/null
+<div class="block">
+ <nav class="panel is-link">
+ {% for release in releases %}
+ <a class="panel-block is-block -between p-4" href="{{ release.url }}">
+ <h5 class="title is-5 mb-2">
+ {{ release }}
+ </h5>
+
+ <div class="level is-mobile">
+ <div class="level-left">
+ <div class="level-item">
+ {% if release.prerelease %}
+ <span class="tag is-danger">{{ _("Development Release") }}</span>
+ {% else %}
+ <span class="tag is-success">{{ _("Stable Release") }}</span>
+ {% end %}
+ </div>
+
+ {# Release Date #}
+ <div class="level-item">
+ {% if release.is_released() %}
+ {{ locale.format_date(release.released_at) }}
+ {% else %}
+ <span class="has-text-warning">
+ {{ _("Not released, yet") }}
+ </span>
+ {% end %}
+ </div>
+ </div>
+ </div>
+ </a>
+ {% end %}
+ </nav>
+</div>
--- /dev/null
+{% extends "../../base.html" %}
+
+{% block title %}{{ distro }} - {{ release }}{% end block %}
+
+{% block body %}
+ <section class="hero is-light">
+ <div class="hero-body">
+ <div class="container">
+ <nav class="breadcrumb" aria-label="breadcrumbs">
+ <ul>
+ <li>
+ <a href="/distros">{{ _("Distributions") }}</a>
+ </li>
+ <li>
+ <a href="/distros/{{ distro.slug }}">
+ {{ distro }}
+ </a>
+ </li>
+ <li>
+ <a href="/distros/{{ distro.slug }}/releases">
+ {{ _("Releases") }}
+ </a>
+ </li>
+ <li class="is-active">
+ <a href="#" aria-current="page">{{ release }}</a>
+ </li>
+ </ul>
+ </nav>
+
+ <h1 class="title">{{ release }}</h1>
+ <h4 class="subtitle is-4">
+ {% if release.prerelease %}
+ <span class="tag is-danger">{{ _("Development Release") }}</span>
+ {% else %}
+ <span class="tag is-success">{{ _("Stable Release") }}</span>
+ {% end %}
+ </h4>
+ </div>
+ </div>
+ </section>
+
+ {# Announcement #}
+ {% if release.announcement %}
+ <section class="section">
+ <div class="container">
+ {% module Text(release.announcement) %}
+ </div>
+ </section>
+ {% end %}
+{% end block %}
"PackageInfo" : packages.InfoModule,
"PackageDependencies": packages.DependenciesModule,
+ # Releases
+ "ReleasesList" : distributions.ReleasesListModule,
+
# Repositories
"ReposList" : repos.ListModule,
(r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/delete", monitorings.DeleteHandler),
(r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/edit", monitorings.EditHandler),
+ # Distro Releases
+ (r"/distros/([A-Za-z0-9\-\.]+)/releases", distributions.ReleasesIndexHandler),
+ (r"/distros/([A-Za-z0-9\-\.]+)/releases/([\w\-_]+)", distributions.ReleasesShowHandler),
+
# Mirrors
(r"/mirrors", mirrors.IndexHandler),
(r"/mirrors/create", mirrors.CreateHandler),
self.redirect("/distros/%s" % distro.slug)
+class ReleasesIndexHandler(base.BaseHandler):
+ def get(self, distro_slug):
+ distro = self.backend.distros.get_by_slug(distro_slug)
+ if not distro:
+ raise tornado.web.HTTPError(404, "Could not find distro: %s" % distro_slug)
+
+ # Pagination
+ limit = self.get_argument_int("limit", 20)
+ offset = self.get_argument_int("offset", None)
+
+ # Fetch releases
+ releases = distro.get_releases(limit=limit, offset=offset)
+
+ self.render("distros/releases/index.html", distro=distro, releases=releases)
+
+
+class ReleasesShowHandler(base.BaseHandler):
+ def get(self, distro_slug, release_slug):
+ distro = self.backend.distros.get_by_slug(distro_slug)
+ if not distro:
+ raise tornado.web.HTTPError(404, "Could not find distro: %s" % distro_slug)
+
+ # Fetch the release
+ release = distro.get_release(release_slug)
+ if not release:
+ raise tornado.web.HTTPError(404, "Could not find release %s" % release_slug)
+
+ # XXX check permissions
+
+ self.render("distros/releases/show.html", distro=distro, release=release)
+
+
class ListModule(ui_modules.UIModule):
def render(self, distros):
return self.render_string("distros/modules/list.html", distros=distros)
+
+
+class ReleasesListModule(ui_modules.UIModule):
+ def render(self, releases):
+ return self.render_string("distros/releases/modules/list.html", releases=releases)