def serialize_object(obj, extra=None, exclude=None): """ Return a generic JSON representation of an object using Django's built-in serializer. (This is used for things like change logging, not the REST API.) Optionally include a dictionary to supplement the object data. A list of keys can be provided to exclude them from the returned dictionary. Private fields (prefaced with an underscore) are implicitly excluded. """ json_str = serialize('json', [obj]) data = json.loads(json_str)[0]['fields'] # Include custom_field_data as "custom_fields" if hasattr(obj, 'custom_field_data'): data['custom_fields'] = data.pop('custom_field_data') # Include any tags. Check for tags cached on the instance; fall back to using the manager. if is_taggable(obj): tags = getattr(obj, '_tags', obj.tags.all()) data['tags'] = [tag.name for tag in tags] # Append any extra data if extra is not None: data.update(extra) # Copy keys to list to avoid 'dictionary changed size during iteration' exception for key in list(data): # Private fields shouldn't be logged in the object change if isinstance(key, str) and key.startswith('_'): data.pop(key) # Explicitly excluded keys if isinstance(exclude, (list, tuple)) and key in exclude: data.pop(key) return data
def prepare_cloned_fields(instance): """ Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where applicable. """ params = {} for field_name in getattr(instance, 'clone_fields', []): field = instance._meta.get_field(field_name) field_value = field.value_from_object(instance) # Swap out False with URL-friendly value if field_value is False: field_value = '' # Omit empty values if field_value not in (None, ''): params[field_name] = field_value # Copy tags if is_taggable(instance): params['tags'] = ','.join([t.name for t in instance.tags.all()]) # Concatenate parameters into a URL query string param_string = '&'.join(['{}={}'.format(k, v) for k, v in params.items()]) return param_string
def prepare_cloned_fields(instance): """ Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where applicable. """ params = [] for field_name in getattr(instance, 'clone_fields', []): field = instance._meta.get_field(field_name) field_value = field.value_from_object(instance) # Swap out False with URL-friendly value if field_value is False: field_value = '' # Omit empty values if field_value not in (None, ''): params.append((field_name, field_value)) # Copy tags if is_taggable(instance): for tag in instance.tags.all(): params.append(('tags', tag.pk)) # Concatenate parameters into a URL query string param_string = '&'.join([f'{k}={v}' for k, v in params]) return param_string
def prepare_cloned_fields(instance): """ Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where applicable. """ params = [] for field_name in getattr(instance, 'clone_fields', []): field = instance._meta.get_field(field_name) field_value = field.value_from_object(instance) # Pass False as null for boolean fields if field_value is False: params.append((field_name, '')) # Omit empty values elif field_value not in (None, ''): params.append((field_name, field_value)) # Copy tags if is_taggable(instance): for tag in instance.tags.all(): params.append(('tags', tag.pk)) # Return a QueryDict with the parameters return QueryDict('&'.join([f'{k}={v}' for k, v in params]), mutable=True)
def handle_deleted_object(sender, instance, **kwargs): """ Fires when an object is deleted. """ # Cache custom fields prior to copying the instance if hasattr(instance, 'cache_custom_fields'): instance.cache_custom_fields() # Create a copy of the object being deleted copy = deepcopy(instance) # Preserve tags if is_taggable(instance): copy.tags = DummyQuerySet(instance.tags.all()) # Queue the copy of the object for processing once the request completes _thread_locals.changed_objects.append( (copy, ObjectChangeActionChoices.ACTION_DELETE))
def serialize_object(obj, extra=None): """ Return a generic JSON representation of an object using Django's built-in serializer. (This is used for things like change logging, not the REST API.) Optionally include a dictionary to supplement the object data. """ json_str = serialize('json', [obj]) data = json.loads(json_str)[0]['fields'] # Include any custom fields if hasattr(obj, 'get_custom_fields'): data['custom_fields'] = { field: str(value) for field, value in obj.cf.items() } # Include any tags if is_taggable(obj): data['tags'] = [tag.name for tag in obj.tags.all()] # Append any extra data if extra is not None: data.update(extra) return data
def get(self, request): model = self.queryset.model content_type = ContentType.objects.get_for_model(model) if self.filterset: self.queryset = self.filterset(request.GET, self.queryset).qs # If this type of object has one or more custom fields, prefetch any relevant custom field values custom_fields = CustomField.objects.filter( obj_type=ContentType.objects.get_for_model(model) ).prefetch_related('choices') if custom_fields: self.queryset = self.queryset.prefetch_related('custom_field_values') # Check for export template rendering if request.GET.get('export'): et = get_object_or_404(ExportTemplate, content_type=content_type, name=request.GET.get('export')) queryset = CustomFieldQueryset(self.queryset, custom_fields) if custom_fields else self.queryset try: return et.render_to_response(queryset) except Exception as e: messages.error( request, "There was an error rendering the selected export template ({}): {}".format( et.name, e ) ) # Check for YAML export support elif 'export' in request.GET and hasattr(model, 'to_yaml'): response = HttpResponse(self.queryset_to_yaml(), content_type='text/yaml') filename = 'netbox_{}.yaml'.format(self.queryset.model._meta.verbose_name_plural) response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) return response # Fall back to built-in CSV formatting if export requested but no template specified elif 'export' in request.GET and hasattr(model, 'to_csv'): response = HttpResponse(self.queryset_to_csv(), content_type='text/csv') filename = 'netbox_{}.csv'.format(self.queryset.model._meta.verbose_name_plural) response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) return response # Provide a hook to tweak the queryset based on the request immediately prior to rendering the object list self.queryset = self.alter_queryset(request) # Compile user model permissions for access from within the template perm_base_name = '{}.{{}}_{}'.format(model._meta.app_label, model._meta.model_name) permissions = {p: request.user.has_perm(perm_base_name.format(p)) for p in ['add', 'change', 'delete']} # Construct the table based on the user's permissions table = self.table(self.queryset) if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): table.columns.show('pk') # Construct queryset for tags list if is_taggable(model): tags = model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name') else: tags = None # Apply the request context paginate = { 'paginator_class': EnhancedPaginator, 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) } RequestConfig(request, paginate).configure(table) context = { 'content_type': content_type, 'table': table, 'permissions': permissions, 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, 'tags': tags, } context.update(self.extra_context()) return render(request, self.template_name, context)