George Notaras avatar George Notaras committed 136204d

Added PostgreSQL Manager code

Comments (0)

Files changed (7)

 
 Then use any web browser to view the exported ``help.html`` file.
 
+
+Configuration
+=============
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': 'primarypanel',                      # Or path to database file if using sqlite3.
+        'USER': 'panel-user',                      # Not used with sqlite3.
+        'PASSWORD': 'XXXXXXXXX',                  # Not used with sqlite3.
+        'HOST': 'localhost',                      # Set to empty string for localhost. Not used with sqlite3.
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+    },
+    # Database settings for pgmanager
+    'pgmanager_conn': {
+        'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': 'postgres',                      # Or path to database file if using sqlite3.
+        'USER': 'administrator',                      # Not used with sqlite3.
+        'PASSWORD': 'XXXXXXX',                  # Not used with sqlite3.
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+        'OPTIONS': {
+            'autocommit': True,
+        },
+    },
+}
+
+
+INSTALLED_APPS = (
+    # My apps here
+    'staticfiles',
+    'PrimaryPanel.unixauth',
+    'PrimaryPanel.dbmanagement',
+    'PostgreSQL_manager',
+    'PrimaryPanel.hosting',
+    'PrimaryPanel.mailadmin',
+    'PrimaryPanel.spamassassin',
+
+)
+
+python manage.py syncdb
+

src/PostgreSQL_manager/admin.py

 #  limitations under the License.
 #
 
+
 from django.contrib import admin
 from django.db.models.loading import cache
+from django.db.utils import DatabaseError, IntegrityError
+from django.contrib import messages
 
+from PostgreSQL_manager.forms import PgUserModelForm
+
+
+
+class PgUserAdmin(admin.ModelAdmin):
+
+    form = PgUserModelForm
+    list_display = ('name', 'is_active', 'connlimit', 'date_created')
+    list_filter = ('is_active', )
+    
+    def get_form(self, request, obj=None, **kwargs):
+        if obj is None:     # This is the add form
+            self.fields = ['name', 'password1', 'password2', 'connlimit', 'is_active']
+        else:               # This is the change form
+            self.fields = ['name', 'password1', 'password2', 'connlimit', 'is_active', 'date_created', 'date_modified']
+            if request.user.is_superuser:
+                self.fields.append('created_by')
+        return super(PgUserAdmin, self).get_form(request, obj, **kwargs)
+    
+    def get_readonly_fields(self, request, obj=None):
+        if obj is None:     # This is the add form
+            self.readonly_fields = []
+        else:               # This is the change form
+            self.readonly_fields = ['date_created', 'date_modified']
+            if request.user.is_superuser:
+                self.readonly_fields.append('created_by')
+        return super(PgUserAdmin, self).get_readonly_fields(request, obj)
+    
+    def queryset(self, request):
+        qs = super(PgUserAdmin, self).queryset(request)
+        if request.user.is_superuser:
+            return qs
+        return qs.filter(created_by=request.user)
+    
+    def save_model(self, request, obj, form, change):
+        PgUser = cache.get_model('pgmanager', 'PgUser')
+        password = form.cleaned_data.get('password1')
+        
+        if not change:  # User creation form
+            
+            assert password != '', 'A password is mandatory for new accounts'
+            
+            # The ``created_by`` attribute is set once at creation time
+            obj.created_by = request.user
+            
+            attrs = 'LOGIN'
+            if not obj.is_active:
+                attrs = 'NOLOGIN'
+            PgUser.objects.create_role(obj.name, password, attrs)
+            
+        else:   # This is the change form
+            
+            if 'connlimit' in form.changed_data:
+                PgUser.objects.limit_connections(obj.name, obj.connlimit)
+            
+            if 'is_active' in form.changed_data:
+                PgUser.objects.lock_unlock_role(obj.name, obj.is_active)
+            
+            if password:
+                PgUser.objects.change_password(obj.name, password)
+            
+            if 'name' in form.changed_data:
+                PgUser.objects.rename_role(form.initial['name'], obj.name)
+ 
+        # Save the model
+        obj.save()
+
+admin.site.register(cache.get_model('pgmanager', 'PgUser'), PgUserAdmin)
+
+
+
+class PgDatabaseAdmin(admin.ModelAdmin):
+
+    readonly_fields = ['name', 'owner', 'date_created', 'date_modified']
+    list_display = ('name', 'owner', 'connlimit', 'date_created')
+    
+    def get_form(self, request, obj=None, **kwargs):
+        if obj is None:     # This is the add form
+            self.fields = ['name', 'owner', 'connlimit']
+        else:               # This is the change form
+            self.fields = ['name', 'owner', 'connlimit', 'date_created', 'date_modified']
+            if request.user.is_superuser:
+                self.fields.append('created_by')
+        return super(PgDatabaseAdmin, self).get_form(request, obj, **kwargs)
+    
+    def get_readonly_fields(self, request, obj=None):
+        if obj is None:     # This is the add form
+            self.readonly_fields = []
+        else:               # This is the change form
+            self.readonly_fields = ['date_created', 'date_modified']
+            if request.user.is_superuser:
+                self.readonly_fields.append('created_by')
+        return super(PgDatabaseAdmin, self).get_readonly_fields(request, obj)
+    
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        PgUser = cache.get_model('pgmanager', 'PgUser')
+        if db_field.name == 'owner':
+            if not request.user.is_superuser:
+                kwargs['queryset'] = PgUser.objects.filter(created_by=request.user)
+        return super(PgDatabaseAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
+        
+    def queryset(self, request):
+        qs = super(PgDatabaseAdmin, self).queryset(request)
+        if request.user.is_superuser:
+            return qs
+        return qs.filter(created_by=request.user)
+    
+    def save_model(self, request, obj, form, change):
+        PgDatabase = cache.get_model('pgmanager', 'PgDatabase')
+        
+        if not change:  # Database creation form
+            
+            # The ``created_by`` attribute is set once at creation time
+            obj.created_by = request.user
+            
+            PgDatabase.objects.create_database(obj.name, obj.owner)
+        
+        else:   # This is the change form
+            
+            if 'owner' in form.changed_data:
+                PgDatabase.objects.set_owner(obj.name, obj.owner)
+            
+            if 'connlimit' in form.changed_data:
+                PgDatabase.objects.limit_connections(obj.name, obj.connlimit)
+            
+            if 'name' in form.changed_data:
+                PgDatabase.objects.rename_database(form.initial['name'], obj.name)
+                
+        # Save the model
+        obj.save()
+
+admin.site.register(cache.get_model('pgmanager', 'PgDatabase'), PgDatabaseAdmin)
+
+

src/PostgreSQL_manager/forms.py

 #  limitations under the License.
 #
 
+
+import string
+
 from django import forms
+from django.db.models.loading import cache
 
+from PostgreSQL_manager import settings
+
+
+class PgUserModelForm(forms.ModelForm):
+
+    class Meta:
+        model = cache.get_model('pgmanager', 'PgUser')
+    
+    # Adds two extra password fields, which will be used for password confirmation.
+    password1 = forms.CharField(label='Password', required=False, widget=forms.PasswordInput, help_text="Valid characters a-z, A-Z, 0-9 and the underscore '_'")
+    password2 = forms.CharField(label='Password (confirm)', required=False, widget=forms.PasswordInput, help_text="Valid characters a-z, A-Z, 0-9 and the underscore '_'")
+    
+    def clean_name(self):
+        name = self.cleaned_data.get('name')
+        if name.lower() in settings.PGMANAGER_FORBIDDEN_USER_NAMES:
+            self._errors['name'] = self.error_class(['This name is reserved for internal use.'])
+        return name
+    
+    def clean_password1(self):
+        """Assures that passord characters are: a-z, A-Z, 0-9
+        """
+        password1 = self.cleaned_data.get('password1')
+        if password1:
+            valid_chars = string.lowercase + string.uppercase + string.digits + '_'
+            for character in password1:
+                if character not in valid_chars:
+                    self._errors['password1'] = self.error_class(['Valid characters a-z, A-Z, 0-9.'])
+        return password1
+        
+    def clean_password2(self):
+        """Cleans the content of the two extra password fields.
+        """
+        password1 = self.cleaned_data.get('password1')
+        password2 = self.cleaned_data.get('password2')
+        
+        # Provided password and confirmation must match
+        if password1 != password2:
+            raise forms.ValidationError('The two password fields do not match.')
+        
+        # Password is required on new records!
+        # If a password has not been set...
+        if not password1 and not password2:
+            if self.instance.pk is None: # ... and this is a new record
+                # Attach error messages to the password fields
+                self._errors['password1'] = self.error_class(['Password must be set on new records.'])
+                self._errors['password2'] = self.error_class(['Password must be set on new records.'])
+                
+        return password2
+
+

src/PostgreSQL_manager/managers.py

+# -*- coding: utf-8 -*-
+#
+#  This file is part of django-postgresql-manager.
+#
+#  django-postgresql-manager - 
+#
+#  Development Web Site:
+#    - http://www.codetrax.org/projects/django-postgresql-manager
+#  Public Source Code Repository:
+#    - https://source.codetrax.org/hgroot/django-postgresql-manager
+#
+#  Copyright 2010 George Notaras <gnot [at] g-loaded.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+from django.db import models
+from django.conf import settings
+from django.db import connections
+
+
+class BasePgManager(models.Manager):
+    
+    PGMANAGER_DB_ALIAS = 'pgmanager_conn'
+    
+    def execute_custom_query(self, sql):
+        connection = connections[self.PGMANAGER_DB_ALIAS]
+        cursor = connection.cursor()
+        cursor.execute(sql)
+
+
+class PgUserManager(BasePgManager):
+    
+    def create_role(self, rolename, password, attrs=''):
+        adminrole = settings.DATABASES[self.PGMANAGER_DB_ALIAS]['USER']
+        self.execute_custom_query(
+            "CREATE ROLE %(rolename)s WITH %(attrs)s PASSWORD '%(password)s';" % {
+                'rolename': rolename, 'password': password, 'attrs': attrs})
+        # Make the admin role member of the new role
+        self.execute_custom_query("GRANT %(rolename)s TO %(adminrole)s" % {
+            'rolename': rolename, 'adminrole': adminrole})
+    
+    def drop_role(self, rolename):
+        self.execute_custom_query("DROP ROLE %s" % rolename)
+    
+    def change_password(self, rolename, password):
+        self.execute_custom_query(
+            "ALTER ROLE %(rolename)s WITH PASSWORD '%(password)s'" % {
+                'rolename': rolename, 'password': password})
+    
+    def rename_role(self, old_name, new_name):
+        self.execute_custom_query(
+            "ALTER ROLE %(old_name)s RENAME TO %(new_name)s" % {
+                'old_name': old_name, 'new_name': new_name})
+    
+    def limit_connections(self, rolename, connlimit):
+        self.execute_custom_query(
+            "ALTER ROLE %(rolename)s WITH CONNECTION LIMIT %(connlimit)s" % {
+                'rolename': rolename, 'connlimit': connlimit})
+    
+    def lock_unlock_role(self, rolename, is_active):
+        login_attr = 'LOGIN'
+        if not is_active:
+            login_attr = 'NOLOGIN'
+        self.execute_custom_query(
+            "ALTER ROLE %(rolename)s WITH %(login_attr)s" % {
+                'rolename': rolename, 'login_attr': login_attr})
+    
+
+
+class PgDatabaseManager(BasePgManager):
+    
+    def create_database(self, database, rolename):
+        self.execute_custom_query(
+            "CREATE DATABASE %(database)s OWNER %(rolename)s" % {
+                'database': database, 'rolename': rolename})
+    
+    def drop_database(self, database):
+        self.execute_custom_query("DROP DATABASE %s" % database)
+    
+    def rename_database(self, old_name, new_name):
+        self.execute_custom_query(
+            "ALTER DATABASE %(old_name)s RENAME TO %(new_name)s" % {
+                'old_name': old_name, 'new_name': new_name})
+    
+    def set_owner(self, database, rolename):
+        self.execute_custom_query(
+            "ALTER DATABASE %(database)s OWNER TO %(rolename)s" % {
+                'database': database, 'rolename': rolename})
+    
+    def limit_connections(self, database, connlimit):
+        self.execute_custom_query(
+            "ALTER DATABASE %(database)s WITH CONNECTION LIMIT %(connlimit)s" % {
+                'database': database, 'connlimit': connlimit})
+    
+

src/PostgreSQL_manager/models.py

 #  limitations under the License.
 #
 
+
 from django.db import models
 from django.db.models import signals
+
+from PostgreSQL_manager import signal_cb
+from PostgreSQL_manager import managers
+
+
+
+class PgUser(models.Model):
+    
+    name = models.SlugField(verbose_name='name', max_length=55, db_index=True, unique=True, help_text='''Enter a name for the PostgreSQL Cluster user''')
+    connlimit = models.IntegerField(default=-1, help_text='''If role is active, this specifies how many concurrent connections the role can make to the server. -1 (the default) means no limit.''')
+    is_active = models.BooleanField(verbose_name='active', default=True, db_index=True)
+
+    date_created = models.DateTimeField(verbose_name='created on', auto_now_add=True)
+    date_modified = models.DateTimeField(verbose_name='last modified on', auto_now=True)
+    created_by = models.ForeignKey('auth.User', related_name='%(class)s_created_by')
+    
+    objects = managers.PgUserManager()
+    
+    class Meta:
+        verbose_name = 'PostgreSQL User'
+        verbose_name_plural = 'PostgreSQL Users'
+    
+    def __unicode__(self):
+        return self.name
+
+signals.pre_delete.connect(signal_cb.dbms_drop_role, sender=PgUser)
+
+
+
+class PgDatabase(models.Model):
+    
+    name = models.SlugField(verbose_name='name', max_length=100, db_index=True, unique=True, help_text='''Enter a name for the PostgreSQL database. Note that the database name will be prefixed with your Primary Panel username.''')
+    owner = models.ForeignKey('pgmanager.PgUser', related_name='%(class)s_owner')
+    connlimit = models.IntegerField(default=-1, help_text='''Enter the number of concurrent connections that can be made to this database. -1 means no limit.''')
+    
+    date_created = models.DateTimeField(verbose_name='created on', auto_now_add=True)
+    date_modified = models.DateTimeField(verbose_name='last modified on', auto_now=True)
+    created_by = models.ForeignKey('auth.User', related_name='%(class)s_created_by')
+    
+    objects = managers.PgDatabaseManager()
+    
+    class Meta:
+        verbose_name = 'PostgreSQL Database'
+        verbose_name_plural = 'PostgreSQL Databases'
+
+    def __unicode__(self):
+        return self.name
+
+signals.pre_delete.connect(signal_cb.dbms_drop_database, sender=PgDatabase)
+

src/PostgreSQL_manager/settings.py

 from django.conf import settings
 
 
-MY_APP_SETTING = getattr(settings, 'MY_APP_SETTING', '...')
+_PGMANAGER_FORBIDDEN_USER_NAMES = (
+    'postgres',
+    'postgresql',
+    'pg',
+    'admin',
+    'administrator',
+    'root',
+    'sys',
+    'system',
+    )
+PGMANAGER_FORBIDDEN_USER_NAMES = getattr(settings, 'PGMANAGER_FORBIDDEN_USER_NAMES', _PGMANAGER_FORBIDDEN_USER_NAMES)
 

src/PostgreSQL_manager/signal_cb.py

 from django.db.models.loading import cache
 
 
-def signal_callback(sender, **kwargs):
-    instance = kwargs['instance']   # app_label.ModelName instance
-    
+def dbms_drop_role(sender, **kwargs):
+    PgUser = cache.get_model('pgmanager', 'PgUser')
+    instance = kwargs['instance']
+    PgUser.objects.drop_role(instance.name)
+
+    
+def dbms_drop_database(sender, **kwargs):
+    PgDatabase = cache.get_model('pgmanager', 'PgDatabase')
+    instance = kwargs['instance']
+    PgDatabase.objects.drop_database(instance.name)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.