Source

django-powerdns-manager / src / powerdns_manager / actions.py

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 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
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# -*- coding: utf-8 -*-
#
#  This file is part of django-powerdns-manager.
#
#  django-powerdns-manager is a web based PowerDNS administration panel.
#
#  Development Web Site:
#    - http://www.codetrax.org/projects/django-powerdns-manager
#  Public Source Code Repository:
#    - https://source.codetrax.org/hgroot/django-powerdns-manager
#
#  Copyright 2012 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.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
from django import template
from django.core.exceptions import PermissionDenied
from django.contrib.admin import helpers
from django.contrib.admin.util import get_deleted_objects, model_ngettext
from django.db import router
from django.shortcuts import render_to_response
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext_lazy, ugettext as _
from django.contrib import messages
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.db.models.loading import cache
from django.core.urlresolvers import reverse

from powerdns_manager.forms import ZoneTypeSelectionForm
from powerdns_manager.forms import TtlSelectionForm
from powerdns_manager.forms import ClonedZoneDomainForm
from powerdns_manager.utils import generate_serial
from powerdns_manager.utils import generate_api_key
from powerdns_manager.utils import interchange_domain



# Action for
# - set change date
# - set serial (?)
# - set TTL to 300, 3600, 86400
#
#def test_action(modeladmin, request, queryset):
#    messages.add_message(request, messages.INFO, 'The test action was successful.')
#test_action.short_description = "Test Action"


def reset_api_key(modeladmin, request, queryset):
    DynamicZone = cache.get_model('powerdns_manager', 'DynamicZone')
    n = queryset.count()
    for domain_obj in queryset:
        # Only one DynamicZone instance for each Domain
        dz = DynamicZone.objects.get(domain=domain_obj)
        if dz.api_key:
            dz.api_key = generate_api_key()
            dz.save()
        else:
            messages.error(request, 'Zone is not dynamic: %s' % domain_obj.name)
            n = n - 1
    if n:
        messages.info(request, 'Successfully updated %d domains.' % n)
reset_api_key.short_description = "Reset API Key"


def set_domain_type_bulk(modeladmin, request, queryset):
    """Actions that sets the domain type on the selected Domain instances.
    
    This action first displays a page which provides a dropdown box for the
    user to select the domain type and then sets the new domain type on the
    sele3cted objects.
    
    It checks if the user has change permission.
    
    Based on: https://github.com/django/django/blob/1.4.2/django/contrib/admin/actions.py
    
    Important
    ---------
    In order to work requires some special form fields (see the template).
    
    """
    opts = modeladmin.model._meta
    app_label = opts.app_label
    
    # Check that the user has change permission for the Domain model
    if not modeladmin.has_change_permission(request):
        raise PermissionDenied
    
    # The user has selected a new domain type through the
    # forms.ZoneTypeSelectionForm form. Make the changes to the selected
    # objects and return a None to display the change list view again.
    #if request.method == 'POST':
    if request.POST.get('post'):
        domain_type = request.POST.get('domaintype')
        n = queryset.count()
        
        if n and domain_type:
            for obj in queryset:
                obj.type = domain_type
                obj.update_serial()
                obj.save()
                obj_display = force_unicode(obj)
                modeladmin.log_change(request, obj, obj_display)
            messages.info(request, 'Successfully updated %d domains.' % n)
        # Return None to display the change list page again.
        return None
    
    info_dict = {
        'form': ZoneTypeSelectionForm(),
        'queryset': queryset,
        'opts': opts,
        'app_label': app_label,
        'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
    }
    return render_to_response(
        'powerdns_manager/actions/set_domain_type.html', info_dict, context_instance=RequestContext(request), mimetype='text/html')
set_domain_type_bulk.short_description = "Set domain type"



def set_ttl_bulk(modeladmin, request, queryset):
    """Actions that resets TTL information on all resource records of the zone
    to the specified value.
    
    This action first displays a page which provides an input box to enter
    the new TTL.
    
    It checks if the user has change permission.
    
    Based on: https://github.com/django/django/blob/1.4.2/django/contrib/admin/actions.py
    
    Important
    ---------
    In order to work requires some special form fields (see the template).
    
    """
    opts = modeladmin.model._meta
    app_label = opts.app_label
    
    Domain = cache.get_model('powerdns_manager', 'Domain')
    Record = cache.get_model('powerdns_manager', 'Record')
    
    perm_domain_change = '%s.%s' % (opts.app_label, opts.get_change_permission())
    perm_record_change = '%s.change_record' % opts.app_label
    
    if not request.user.has_perms([perm_domain_change, perm_record_change]):
        raise PermissionDenied
    
    # Check that the user has change permission for the Re model
    if not modeladmin.has_change_permission(request):
        raise PermissionDenied
    
    # The user has set a new TTL value through the forms.TtlSelectionForm form.
    # Make the changes to the selected objects and return a None to display the
    # change list view again.
    #if request.method == 'POST':
    if request.POST.get('post'):
        form = TtlSelectionForm(request.POST)
        if form.is_valid():
            new_ttl = form.cleaned_data['new_ttl']
            reset_zone_minimum = form.cleaned_data['reset_zone_minimum']
            
            n = queryset.count()
            record_count = 0
            
            if n and new_ttl:
                for domain_obj in queryset:
                    # Find all resource records of this domain
                    qs = Record.objects.filter(domain=domain_obj)
                    # Now set the new TTL
                    for rr in qs:
                        rr.ttl = int(new_ttl)
                        # If this is the SOA record and ``reset_zone_minimum`` has
                        # been checked, set the minimum TTL of the SOA record equal
                        # to the ``new_ttl`` value
                        #
                        # Important: We do not call ``models.Domain.set_minimum_ttl()``
                        # because we edit the SOA record here.
                        #
                        if reset_zone_minimum and rr.type == 'SOA':
                            bits = rr.content.split()
                            # SOA content:  primary hostmaster serial refresh retry expire default_ttl
                            bits[6] = str(new_ttl)
                            rr.content = ' '.join(bits)
                        # Save the resource record
                        rr.save()
                        rr_display = force_unicode(rr)
                        modeladmin.log_change(request, rr, rr_display)
                    
                    # Update the domain serial
                    domain_obj.update_serial()
                    
                    record_count += len(qs)
                messages.info(request, 'Successfully updated %d zones (%d total records).' % (n, record_count))
            # Return None to display the change list page again.
            return None
    else:
        form = TtlSelectionForm()
    
    info_dict = {
        'form': form,
        'queryset': queryset,
        'opts': opts,
        'app_label': app_label,
        'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
    }
    return render_to_response(
        'powerdns_manager/actions/set_ttl.html', info_dict, context_instance=RequestContext(request), mimetype='text/html')
set_ttl_bulk.short_description = "Set Resource Records TTL"



def force_serial_update(modeladmin, request, queryset):
    """Action that updates the serial resets TTL information on all resource
    records of the selected zones.
    """
    for domain in queryset:
        domain.update_serial()
    n = queryset.count()
    messages.info(request, 'Successfully updated %d zones.' % n)
force_serial_update.short_description = "Force serial update"



def clone_zone(modeladmin, request, queryset):
    """Actions that clones the selected zone.
    
    Accepts only one selected zone.
    
    Clones:
    
      - Resource Records
      - Dynamic setting
      - Domain Metadata
    
    This action first displays a page which provides an input box to enter
    the origin of the new zone.
    
    It checks if the user has add & change permissions.
    
    It checks if a zone with the name that has been entered as new exists in
    the database.
    
    Based on: https://github.com/django/django/blob/1.4.2/django/contrib/admin/actions.py
    
    Important
    ---------
    In order to work requires some special form fields (see the template).
    
    """
    opts = modeladmin.model._meta
    app_label = opts.app_label
    
    Domain = cache.get_model('powerdns_manager', 'Domain')
    Record = cache.get_model('powerdns_manager', 'Record')
    DynamicZone = cache.get_model('powerdns_manager', 'DynamicZone')
    DomainMetadata = cache.get_model('powerdns_manager', 'DomainMetadata')
    
    # Check the number of selected zones. This action can work on a single zone.
    
    n = queryset.count()
    if n != 1:
        messages.error(request, 'Only one zone may be selected for cloning.')
        return None
    
    # Check permissions
        
    perm_domain_add = '%s.%s' % (opts.app_label, opts.get_add_permission())
    perm_domain_change = '%s.%s' % (opts.app_label, opts.get_change_permission())
    perm_record_add = '%s.add_record' % opts.app_label
    perm_record_change = '%s.change_record' % opts.app_label
    
    if not request.user.has_perms(
            [perm_domain_add, perm_domain_change, perm_record_add, perm_record_change]):
        raise PermissionDenied
    
    # Check that the user has change permission for the add and change modeladmin forms
    if not modeladmin.has_add_permission(request):
        raise PermissionDenied
    if not modeladmin.has_change_permission(request):
        raise PermissionDenied
    
    # The user has set a domain name for the clone through the forms.ClonedZoneDomainForm form.
    #if request.method == 'POST':
    if request.POST.get('post'):
        form = ClonedZoneDomainForm(request.POST)
        if form.is_valid():
            
            # Store Data from the form
            
            # Store the new domain name for the clone.
            clone_domain_name = form.cleaned_data['clone_domain_name']
            
            if not clone_domain_name:
                return None # Should never happen

            option_clone_dynamic = form.cleaned_data['option_clone_dynamic']
            option_clone_metadata = form.cleaned_data['option_clone_metadata']
            
            # Clone base zone
            
            # At this point queryset contain exactly one object. Checked above.
            domain_obj = queryset[0]
            
            # Create the clone (Check for uniqueness takes place in forms.ClonedZoneDomainForm 
            clone_obj = Domain.objects.create(
                name = clone_domain_name,
                master = domain_obj.master,
                #last_check = domain_obj.last_check,
                type = domain_obj.type,
                #notified_serial = domain_obj.notified_serial,
                account = domain_obj.account,
                created_by = request.user   # We deliberately do not use the domain_obj.created_by
            )
            modeladmin.log_addition(request, clone_obj)
            
            # Clone Resource Records
            
            # Find all resource records of this domain
            domain_rr_qs = Record.objects.filter(domain=domain_obj)
            
            # Create the clone's RRs
            for rr in domain_rr_qs:
                
                # Construct RR name with interchanged domain
                clone_rr_name = interchange_domain(rr.name, domain_obj.name, clone_domain_name)
                
                # Special treatment to the content of SOA and SRV RRs
                if rr.type == 'SOA':
                    content_parts = rr.content.split()
                    # primary
                    content_parts[0] = interchange_domain(content_parts[0], domain_obj.name, clone_domain_name)
                    # hostmaster
                    content_parts[1] = interchange_domain(content_parts[1], domain_obj.name, clone_domain_name)
                    # Serial. Set new serial
                    content_parts[2] = generate_serial()
                    clone_rr_content = ' '.join(content_parts)
                elif rr.type == 'SRV':
                    content_parts = rr.content.split()
                    # target
                    content_parts[2] = interchange_domain(content_parts[2], domain_obj.name, clone_domain_name)
                    clone_rr_content = ' '.join(content_parts)
                else:
                    clone_rr_content = interchange_domain(rr.content, domain_obj.name, clone_domain_name)
                
                # Create and save the cloned record.
                clone_rr = Record(
                    domain = clone_obj,
                    name = clone_rr_name,
                    type = rr.type,
                    content = clone_rr_content,
                    ttl = rr.ttl,
                    prio = rr.prio,
                    auth = rr.auth,
                    ordername = rr.ordername
                )
                clone_rr.save()
                #modeladmin.log_addition(request, clone_rr)
            
            # Clone Dynamic Zone setting
            
            if option_clone_dynamic:
                
                # Get the base domain's dynamic zone.
                # There is only one Dynamic Zone object for each zone.
                domain_dynzone_obj = DynamicZone.objects.get(domain=domain_obj)
                
                # Create and save the dynamic zone object for the clone.
                clone_dynzone_obj = DynamicZone(
                    domain = clone_obj,
                    is_dynamic = domain_dynzone_obj.is_dynamic
                    )
                clone_dynzone_obj.save()
            
            # Clone the zone's metadata
            
            if option_clone_metadata:
                
                # Get the base domain's metadata object.
                # There is only one metadata object for each zone.
                domain_metadata_obj = DomainMetadata.objects.get(domain=domain_obj)
                
                # Create and save the metadata object for the clone.
                clone_metadata_obj = DomainMetadata(
                    domain = clone_obj,
                    kind = domain_metadata_obj.kind,
                    content = domain_metadata_obj.content
                    )
                clone_metadata_obj.save()
            
            messages.info(request, 'Successfully cloned %s zone to %s' % \
                (domain_obj.name, clone_domain_name))
            
            # Redirect to the new zone's change form.
            return HttpResponseRedirect(reverse('admin:%s_domain_change' % app_label, args=(clone_obj.id,)))
    
    else:
        form = ClonedZoneDomainForm()
    
    info_dict = {
        'form': form,
        'queryset': queryset,
        'opts': opts,
        'app_label': app_label,
        'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
    }
    return render_to_response(
        'powerdns_manager/actions/clone_zone.html', info_dict, context_instance=RequestContext(request), mimetype='text/html')
clone_zone.short_description = "Clone the selected zone"