text:
'use strict';

const _ = require('lodash');
const response = require('reqres').res;
const request = require('reqres').req;
const proxyquire = require('proxyquire');
const ErrorClass = require('../../../controller/validation-error');

const BaseController = require('../../../controller/base-controller');

describe('controller', () => {
  let Controller;
  let controller;

  beforeEach(() => {
    Controller = proxyquire('../../../controller/controller', {
      './base-controller': BaseController
    });
    sinon.stub(BaseController.prototype, 'use');
    sinon.stub(BaseController.prototype, 'locals').returns({foo: 'bar'});
  });

  it('sets the correct error class to the instance', () => {
    controller = new Controller({});
    controller.ValidationError.should.equal(ErrorClass);
  });

  afterEach(() => {
    BaseController.prototype.use.restore();
    BaseController.prototype.locals.restore();
  });

  describe('methods', () => {
    beforeEach(() => {
      sinon.stub(BaseController.prototype, 'getNextStep');
    });

    afterEach(() => {
      BaseController.prototype.getNextStep.restore();
    });

    describe('.get()', () => {
      const req = {};

      let res;

      beforeEach(() => {
        sinon.stub(BaseController.prototype, 'get');
        controller = new Controller({
          template: 'foo'
        });
        res = response({
          locals: {
            partials: {
              step: 'default-template'
            }
          }
        });
      });

      afterEach(() => {
        BaseController.prototype.get.restore();
      });

      it('calls super', () => {
        controller.get(req, res, _.noop);
        BaseController.prototype.get.should.have.been.calledOnce
          .and.calledWithExactly(req, res, _.noop);
      });

      it('calls res.render with the template', () => {
        controller.get(req, res, _.noop);
        res.render.should.have.been.calledOnce;
      });

      it('sets template to res.locals.partials.step if view lookup error', () => {
        res.render = (template, cb) => cb(new Error('Failed to lookup view'));
        controller.get(req, res, _.noop);
        controller.options.template.should.be.equal('default-template');
      });
    });

    describe('.getBackLink()', () => {
      const req = {};
      const res = {
        locals: {}
      };

      beforeEach(() => {
        res.locals.backLink = '';
        req.baseUrl = '/base';
        req.params = {};
        controller = new Controller({
          template: 'foo'
        });
      });

      it('returns an empty string if res.locals.backLink is an empty string', () => {
        controller.getBackLink(req, res).should.be.equal('');
      });

      it('returns null if res.locals.backLink is null', () => {
        res.locals.backLink = null;
        should.not.exist(controller.getBackLink(req, res));
      });

      it('returns the backLink unaltered if not editing and baseUrl is set', () => {
        res.locals.backLink = 'backLink';
        controller.getBackLink(req, res).should.be.equal('backLink');
      });

      it('prepends a slash if baseUrl is /', () => {
        res.locals.backLink = 'backLink';
        req.baseUrl = '/';
        controller.getBackLink(req, res).should.be.equal('/backLink');
      });

      it('prepends a slash if baseUrl is an empty string', () => {
        res.locals.backLink = 'backLink';
        req.baseUrl = '';
        controller.getBackLink(req, res).should.be.equal('/backLink');
      });

      it('appends /edit if editing', () => {
        req.params.action = 'edit';
        res.locals.backLink = 'backLink';
        controller.getBackLink(req, res).should.be.equal('backLink/edit');
      });

      it('appends /edit and prepends a slash if editing and baseUrl not set', () => {
        req.params.action = 'edit';
        req.baseUrl = '/';
        res.locals.backLink = 'backLink';
        controller.getBackLink(req, res).should.be.equal('/backLink/edit');
      });
    });

    describe('.locals()', () => {
      let req;
      let res;

      beforeEach(done => {
        req = {
          form: {
            errors: {}
          },
          translate: () => '',
          params: {}
        };
        res = response();
        sinon.stub(Controller.prototype, 'getBackLink');
        controller = new Controller({
          template: 'foo',
          route: '/bar'
        });
        controller._configure(req, res, done);
      });

      afterEach(() => {
        Controller.prototype.getBackLink.restore();
      });

      it('always extends from parent locals', () => {
        controller.locals(req, res).should.have.property('foo').and.always.equal('bar');
      });

      it('calls getBackLink', () => {
        controller.locals(req, res);
        Controller.prototype.getBackLink.should.have.been.calledOnce;
      });

      it('returns errorLength.single if there is one error', () => {
        req.form.errors = {
          one: true
        };
        controller.locals(req, res).should.have.property('errorLength')
          .and.deep.equal({
            single: true
          });
      });

      it('returns errorLength.multiple if there is more than one error', () => {
        req.form.errors = {
          one: true,
          two: true
        };
        controller.locals(req, res).should.have.property('errorLength')
          .and.deep.equal({
            multiple: true
          });
      });

      it('returns errorLength undefined if req.form.errors is not set', () => {
        delete req.form.errors;
        controller.locals(req, res).should.have.property('errorLength')
          .and.be.undefined;
      });

      describe('with fields', () => {
        let locals;
        beforeEach(() => {
          req.form.options.fields = {
            'a-field': {
              mixin: 'input-text',
              foo: 'bar'
            },
            'another-field': {
              mixin: 'input-number',
              disableRender: true
            }
          };
          locals = controller.locals(req, res);
        });

        it('should have added a fields array to return object', () => {
          locals.should.have.property('fields').and.be.an('array');
          locals.fields[0].should.be.eql(Object.assign({}, req.form.options.fields['a-field'], { key: 'a-field' }));
          locals.fields[1].should.be.eql(Object.assign({},
            req.form.options.fields['another-field'],
            { key: 'another-field' }
          ));
        });
      });

      describe('with locals', () => {
        beforeEach(() => {
          res.locals = {};
          res.locals.values = {
            'field-one': 1,
            'field-two': 2,
            'field-three': 3,
            'field-four': 4
          };

          req.form.options = {
            steps: {
              '/one': {
                fields: ['field-one', 'field-two']
              },
              '/two': {
                fields: ['field-three', 'field-four']
              }
            },
            locals: {
              test: 'bar'
            },
            route: '/baz'
          };
        });

        it('should expose test in locals', () => {
          controller.locals(req, res).should.have.property('test').and.equal('bar');
        });
      });
    });

    describe('.getNextStep()', () => {
      let req;
      let res;
      let getStub;

      beforeEach(done => {
        getStub = sinon.stub();
        getStub.returns(['/']);
        req = request();
        res = response();
        req.sessionModel = {
          reset: sinon.stub(),
          get: getStub
        };
        controller = new Controller({template: 'foo'});
        BaseController.prototype.getNextStep.returns('/');
        BaseController.prototype.getValues = function (myReq, myRes, callback) {
          callback();
        };
        controller._configure(req, res, done);
      });

      describe('when the action is "edit"', () => {
        it('appends "edit" to the path', () => {
          req.form.options.continueOnEdit = true;
          req.params.action = 'edit';
          controller.getNextStep(req).should.contain('/edit');
        });
      });

      describe('when the action is "edit" and continueOnEdit option is falsey', () => {
        it('appends "confirm" to the path', () => {
          req.form.options.continueOnEdit = false;
          req.params.action = 'edit';
          controller.getNextStep(req).should.contain('/confirm');
        });
      });

      describe('when the action is "edit" and continueOnEdit is truthy', () => {
        it('appends "/edit" to the path if next page is not /confirm', () => {
          BaseController.prototype.getNextStep.returns('/step');
          req.form.options.continueOnEdit = true;
          req.params.action = 'edit';
          getStub.returns(['/step']);
          controller.getNextStep(req).should.contain('/edit');
        });

        it('doesn\'t append "/edit" to the path if next page is /confirm', () => {
          BaseController.prototype.getNextStep.returns('/confirm');
          req.form.options.continueOnEdit = true;
          req.params.action = 'edit';
          controller.getNextStep(req).should.not.contain('/edit');
        });
      });

      describe('with a fork', () => {
        beforeEach(() => {
          getStub = sinon.stub();
          req.sessionModel = {
            reset: sinon.stub(),
            get: getStub
          };
          req.form.values = {};
          req.form.historicalValues = {};
          BaseController.prototype.getNextStep.returns('/next-page');
        });

        describe('when the condition config is met', () => {
          it('the next step is the fork target', () => {
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition: {
                field: 'example-radio',
                value: 'superman'
              }
            }];
            controller.getNextStep(req, {}).should.contain('/target-page');
          });
        });

        describe('when the condition config is met by a historical form', () => {
          it('the next step is the fork target', () => {
            req.form.historicalValues = {'example-radio': 'superman'};
            req.form.options.forks = [{
              target: '/target-page',
              condition: {
                field: 'example-radio',
                value: 'superman'
              }
            }];
            controller.getNextStep(req, {}).should.contain('/target-page');
          });
        });

        describe('when the condition config is not met', () => {
          it('the next step is the original next target', () => {
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition: {
                field: 'example-radio',
                value: 'lex luther'
              }
            }];
            controller.getNextStep(req, {}).should.equal('/next-page');
          });
        });

        describe('when the condition config is not met by a historical form', () => {
          it('the next step is the original next target', () => {
            req.form.historicalValues = {'example-radio': 'superman'};
            req.form.options.forks = [{
              target: '/target-page',
              condition: {
                field: 'example-radio',
                value: 'lex luther'
              }
            }];
            controller.getNextStep(req, {}).should.equal('/next-page');
          });
        });

        describe('when the condition config is not met and historical values are not present', () => {
          it('the next step is the original next target', () => {
            req.form.historicalValues = null;
            req.form.options.forks = [{
              target: '/target-page',
              condition: {
                field: 'example-radio',
                value: 'lex luther'
              }
            }];
            controller.getNextStep(req, {}).should.equal('/next-page');
          });
        });

        describe('when the condition is => met', () => {
          it('the next step is the fork target', () => {
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            controller.getNextStep(req, {}).should.contain('/target-page');
          });
        });

        describe('when the condition is => not met', () => {
          it('the next step is the origin next target', () => {
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'batman';
              }
            }];
            controller.getNextStep(req, {}).should.equal('/next-page');
          });
        });

        describe('when the action is "edit" and we\'ve been down the fork', () => {
          it('should return /confirm if baseUrl is not set', () => {
            getStub.returns(['/target-page']);
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            req.form.options.continueOnEdit = false;
            req.params.action = 'edit';
            controller.getNextStep(req).should.equal('/confirm');
          });

          it('should follow fork if fork has `continueOnEdit` set to true', () => {
            getStub.returns(['/target-page']);
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              continueOnEdit: true,
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            req.form.options.continueOnEdit = false;
            req.params.action = 'edit';
            controller.getNextStep(req).should.equal('/target-page/edit');
          });

          it('should return /a-base-url/confirm if baseUrl is set', () => {
            getStub.returns(['/target-page']);
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            req.form.options.continueOnEdit = false;
            req.params.action = 'edit';
            req.baseUrl = '/a-base-url';
            controller.getNextStep(req).should.equal('/a-base-url/confirm');
          });

          it('should append "edit" to the path if baseUrl is set and continueOnEdit is false', () => {
            getStub.returns(['/target-page']);
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            req.form.options.continueOnEdit = true;
            req.params.action = 'edit';
            req.baseUrl = '/a-base-url';
            controller.getNextStep(req).should.equal('/a-base-url/target-page/edit');
          });
        });

        describe('when the action is "edit" but we\'ve not been down the fork', () => {
          it('appends "edit" to the path', () => {
            req.form.values['example-radio'] = 'superman';
            req.form.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            req.form.options.continueOnEdit = false;
            req.params.action = 'edit';
            controller.getNextStep(req).should.contain('/target-page');
          });
        });

        describe('when the action is "edit" and we\'ve been down the standard path', () => {
          it('appends "edit" to the path', () => {
            getStub.returns(['/next-page']);
            req.form.values['example-radio'] = 'clark-kent';
            controller.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            controller.options.continueOnEdit = false;
            req.params.action = 'edit';
            controller.getNextStep(req).should.contain('/confirm');
          });
        });

        describe('when the action is "edit" but we\'ve not been down the standard path', () => {
          it('appends "edit" to the path', () => {
            req.form.values['example-radio'] = 'clark-kent';
            controller.options.forks = [{
              target: '/target-page',
              condition(r) {
                return r.form.values['example-radio'] === 'superman';
              }
            }];
            controller.options.continueOnEdit = false;
            req.params.action = 'edit';
            controller.getNextStep(req).should.contain('/next-page');
          });
        });
      });

      describe('with more than one fork', () => {
        describe('when the fields are the same', () => {
          beforeEach(() => {
            req.form.values = {
              'example-radio': 'superman'
            };
            req.form.options.forks = [{
              target: '/superman-page',
              condition: {
                field: 'example-radio',
                value: 'superman'
              }
            }, {
              target: '/batman-page',
              condition: {
                field: 'example-radio',
                value: 'superman'
              }
            }];
          });

          describe('and each condition is met', () => {
            it('the last forks\' target becomes the next step', () => {
              controller.getNextStep(req, {}).should.contain('/batman-page');
            });
          });
        });

        describe('when the fields are different', () => {
          beforeEach(() => {
            req.form.options.forks = [{
              target: '/superman-page',
              condition: {
                field: 'example-radio',
                value: 'superman'
              }
            }, {
              target: '/smallville-page',
              condition: {
                field: 'example-email',
                value: 'clarke@smallville.com'
              }
            }];
          });

          describe('and each condition is met', () => {
            beforeEach(() => {
              req.form.values = {
                'example-radio': 'superman',
                'example-email': 'clarke@smallville.com'
              };
            });
            it('the last forks\' target becomes the next step', () => {
              controller.getNextStep(req, {}).should.contain('/smallville-page');
            });
          });

          describe('and the first condition is met', () => {
            beforeEach(() => {
              req.form.values = {
                'example-radio': 'superman',
                'example-email': 'kent@smallville.com'
              };
            });
            it('the first forks\' target becomes the next step', () => {
              controller.getNextStep(req, {}).should.contain('/superman-page');
            });
          });
        });
      });
    });

    describe('.getErrorStep()', () => {
      const req = {};
      const res = {};
      const err = {};

      beforeEach(done => {
        sinon.stub(BaseController.prototype, 'getErrorStep').returns('/');
        req.params = {};
        controller = new Controller({template: 'foo'});
        controller._configure(req, res, done);
      });

      afterEach(() => {
        BaseController.prototype.getErrorStep.restore();
      });

      describe('when the action is "edit" and the parent redirect is not edit', () => {
        it('appends "edit" to the path', () => {
          req.params.action = 'edit';
          controller.getErrorStep(err, req).should.match(/\/edit$/);
        });

        it('doesn\'t append "edit" to the path if "edit" is already present', () => {
          req.params.action = 'edit';
          BaseController.prototype.getErrorStep.returns('/a-path/edit/id');
          controller.getErrorStep(err, req).should.not.match(/\/edit$/);
        });
      });
    });

    describe('getHeader()', () => {
      let lookup;
      beforeEach(() => {
        controller = new Controller({template: 'foo'});
        lookup = sinon.stub();
      });

      it('calls lookup with the correct list of keys', () => {
        const expected = 'pages.step-one.header';
        controller.getHeader('step-one', lookup);
        lookup.firstCall.args[0].should.equal(expected);
      });

      it('passes locals too lookup as second arg', () => {
        const locals = {};
        controller.getHeader('step-one', lookup, locals);
        lookup.firstCall.args[1].should.be.equal(locals);
      });
    });

    describe('getCaptionHeading()', () => {
      let lookup;
      beforeEach(() => {
        controller = new Controller({template: 'foo'});
        lookup = sinon.stub();
      });

      it('calls lookup with the correct list of keys', () => {
        const expected = 'pages.step-one.captionHeading';
        controller.getCaptionHeading('step-one', lookup);
        lookup.firstCall.args[0].should.equal(expected);
      });

      it('passes locals too lookup as second arg', () => {
        const locals = {};
        controller.getCaptionHeading('step-one', lookup, locals);
        lookup.firstCall.args[1].should.be.equal(locals);
      });
    });

    describe('getSubHeading()', () => {
      let lookup;
      beforeEach(() => {
        controller = new Controller({template: 'foo'});
        lookup = sinon.stub();
      });

      it('calls lookup with the correct list of keys', () => {
        const expected = 'pages.step-one.subHeading';
        controller.getSubHeading('step-one', lookup);
        lookup.firstCall.args[0].should.equal(expected);
      });

      it('passes locals too lookup as second arg', () => {
        const locals = {};
        controller.getSubHeading('step-one', lookup, locals);
        lookup.firstCall.args[1].should.be.equal(locals);
      });
    });

    describe('getTitle()', () => {
      let lookup;
      let fields;
      beforeEach(() => {
        controller = new Controller({template: 'foo'});
        lookup = sinon.stub();
        fields = {
          'field-one': {}
        };
      });

      it('calls lookup with the correct list of keys', () => {
        const expected = [
          'pages.step-one.header',
          'fields.field-one.label',
          'fields.field-one.legend'
        ];
        controller.getTitle('step-one', lookup, fields);
        lookup.firstCall.args[0].should.be.deep.equal(expected);
      });

      it('passes the locals hash to lookup as second arg', () => {
        const locals = {};
        controller.getTitle('step-one', lookup, fields, locals);
        lookup.firstCall.args[1].should.be.equal(locals);
      });
    });

    describe('getIntro()', () => {
      let lookup;
      beforeEach(() => {
        controller = new Controller({template: 'foo'});
        lookup = sinon.stub();
      });

      it('calls lookup with the correct list of keys', () => {
        const expected = [
          'pages.step-one.intro'
        ];
        controller.getIntro('step-one', lookup);
        lookup.firstCall.args[0].should.be.deep.equal(expected);
      });

      it('passes locals too lookup as second arg', () => {
        const locals = {};
        controller.getIntro('step-one', lookup, locals);
        lookup.firstCall.args[1].should.be.equal(locals);
      });
    });

    describe('getErrors', () => {
      let req;
      let res;
      let errors;

      beforeEach(() => {
        errors = {
          key1: {
            key: 'key1',
            type: 'required'
          },
          key2: {
            key: 'key2',
            type: 'maxlength',
            arguments: [10]
          },
          key3: {
            key: 'key3',
            type: 'before',
            arguments: [3, 'years']
          }
        };

        req = request();
        req.form = {};
        res = response();
        sinon.stub(controller, 'getErrors').returns(errors);
        sinon.stub(controller, 'getErrorMessage').callsFake(e => `error for ${e.key}`);
      });

      afterEach(() => {
        controller.getErrors.restore();
        controller.getErrorMessage.restore();
      });

      it('sets response from `getErrors` onto req.form.errors', done => {
        controller._getErrors(req, res, () => {
          req.form.errors.should.have.keys('key1', 'key2', 'key3');
          done();
        });
      });

      it('adds a message property onto each error', done => {
        controller._getErrors(req, res, () => {
          req.form.errors.should.have.keys('key1', 'key2', 'key3');

          req.form.errors.key1.should.have.a.property('message');
          controller.getErrorMessage.should.have.been.calledWith(errors.key1, req, res);
          req.form.errors.key1.message.should.equal('error for key1');

          req.form.errors.key2.should.have.a.property('message');
          controller.getErrorMessage.should.have.been.calledWith(errors.key2, req, res);
          req.form.errors.key2.message.should.equal('error for key2');

          req.form.errors.key3.should.have.a.property('message');
          controller.getErrorMessage.should.have.been.calledWith(errors.key3, req, res);
          req.form.errors.key3.message.should.equal('error for key3');
          done();
        });
      });
    });

    describe('getErrorLength', () => {
      let req;
      let res;

      beforeEach(() => {
        req = request();
        req.form = {};
        res = response();
      });

      it('returns `{single:true}` if only one error is present', () => {
        req.form.errors = {
          one: {
            key: 'one'
          }
        };
        controller.getErrorLength(req, res).should.eql({ single: true });
      });

      it('returns `{multiple:true}` if more than one error is present', () => {
        req.form.errors = {
          one: {
            key: 'one'
          },
          two: {
            key: 'two'
          }
        };
        controller.getErrorLength(req, res).should.eql({ multiple: true });
      });

      it('returns undefined if no errors are present', () => {
        expect(controller.getErrorLength(req, res)).to.equal(undefined);
      });
    });

    describe('getErrorMessage', () => {
      let req;
      let res;

      beforeEach(() => {
        req = request();
        res = response();
        req.translate = sinon.stub().returns('');
      });

      it('uses the current error object properties to translate the message', () => {
        req.translate.withArgs('validation.key.custom').returns('This is a custom message');
        const error = new ErrorClass('key', { type: 'custom' });
        controller.getErrorMessage(error, req, res).should.equal('This is a custom message');
      });

      it('uses default error message for field if no field and type specific message is defined', () => {
        req.translate.withArgs('validation.key.default').returns('Default field message');
        const error = new ErrorClass('key', { type: 'required' });
        controller.getErrorMessage(error, req, res).should.equal('Default field message');
      });

      it('uses default error message for validation type if no field level message is defined', () => {
        req.translate.withArgs('validation.required').returns('Default required message');
        const error = new ErrorClass('key', { type: 'required' });
        controller.getErrorMessage(error, req, res).should.equal('Default required message');
      });

      it('uses global default error message if no type of field level messages are defined', () => {
        req.translate.withArgs('validation.default').returns('Global default');
        const error = new ErrorClass('key', { type: 'required' });
        controller.getErrorMessage(error, req, res).should.equal('Global default');
      });

      it('populates messages with field label', () => {
        req.translate.withArgs('validation.key.required').returns('Your {{label}} is required');
        req.translate.withArgs('fields.key.label').returns('Field label');
        const error = new ErrorClass('key', { type: 'required' });
        controller.getErrorMessage(error, req, res).should.equal('Your field label is required');
      });

      it('populates messages with legend', () => {
        req.translate.withArgs('validation.key.required').returns('Your {{legend}} is required');
        req.translate.withArgs('fields.key.legend').returns('date');
        const error = new ErrorClass('key', { type: 'required' });
        controller.getErrorMessage(error, req, res).should.equal('Your date is required');
      });

      it('populates maxlength messages with the maximum length', () => {
        req.translate.withArgs('validation.key.maxlength').returns('This must be less than {{maxlength}} characters');
        const error = new ErrorClass('key', { type: 'maxlength', arguments: [10] });
        controller.getErrorMessage(error, req, res).should.equal('This must be less than 10 characters');
      });

      it('populates minlength messages with the minimum length', () => {
        const message = 'This must be no more than {{minlength}} characters';
        req.translate.withArgs('validation.key.minlength').returns(message);
        const error = new ErrorClass('key', { type: 'minlength', arguments: [10] });
        controller.getErrorMessage(error, req, res).should.equal('This must be no more than 10 characters');
      });

      it('populates exactlength messages with the required length', () => {
        req.translate.withArgs('validation.key.exactlength').returns('This must be {{exactlength}} characters');
        const error = new ErrorClass('key', { type: 'exactlength', arguments: [10] });
        controller.getErrorMessage(error, req, res).should.equal('This must be 10 characters');
      });

      it('populates maxword messages with the required length', () => {
        req.translate.withArgs('validation.key.maxword').returns('This must be {{maxword}} words');
        const error = new ErrorClass('key', { type: 'maxword', arguments: [10] });
        controller.getErrorMessage(error, req, res).should.equal('This must be 10 words');
      });

      it('populates before messages with the required difference', () => {
        req.translate.withArgs('validation.key.before').returns('This must be more than {{diff}} ago');
        const error = new ErrorClass('key', { type: 'before', arguments: [5, 'days'] });
        controller.getErrorMessage(error, req, res).should.equal('This must be more than 5 days ago');
      });

      it('populates after messages with the required difference', () => {
        req.translate.withArgs('validation.key.after').returns('This must be less than {{diff}} ago');
        const error = new ErrorClass('key', { type: 'after', arguments: [5, 'days'] });
        controller.getErrorMessage(error, req, res).should.equal('This must be less than 5 days ago');
      });

      it('populates custom messages with the required constiable', () => {
        req.translate.withArgs('validation.key.custom').returns('This must be {{custom}}');
        const error = new ErrorClass('key', { type: 'custom', arguments: ['dynamic'] });
        controller.getErrorMessage(error, req, res).should.equal('This must be dynamic');
      });

      it('populates messages with values from `res.locals` when present', () => {
        req.translate.withArgs('validation.key.required').returns('This must be a {{something}}');
        res.locals.something = 'value';
        const error = new ErrorClass('key', { type: 'required' });
        controller.getErrorMessage(error, req, res).should.equal('This must be a value');
      });
    });
  });
});


text:
// BHSegmentItem.h
//
// Copyright (c) 2017 zhaobinhua <zbh1019@163.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface BHSegmentItem : NSObject

/** 按钮标题*/
@property (nonatomic, strong) NSString *title;  // Default @""
/** 按钮标题颜色*/
@property (nonatomic, strong) UIColor *titleColor;  // Default [UIColor redColor]
/** 按钮标题选中颜色*/
@property (nonatomic, strong) UIColor *selectColor; // Default [UIColor grayColor]
/** 按钮标题字体*/
@property (nonatomic, strong) UIFont *font; // Default [UIFont systemFontOfSize:14]

/** 小竖条背景图片*/
@property (nonatomic, strong) UIColor *verticalBarColor;    // Default [UIColor grayColor]
/** 小竖条宽, 高*/
@property (nonatomic, assign) CGSize verticalBarSize;   // Default CGSizeZero;
/** 小竖条背景图片*/
@property (nonatomic, strong) UIImage *verticalBarImage;  // Default nil

/** 该属性指向所赋值的Button, 以便于设置BadgeValue*/
@property (nonatomic, strong) UIButton *segmentButton;  // Default nil
/** 设置BadgeValue */
@property (nonatomic, assign) NSInteger badgeValue;     // Default 0

@end

redpajama_set_name: RedPajamaGithub


text:
from __future__ import unicode_literals

from datetime import date

from django import forms
from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter
from django.contrib.admin.options import (
    HORIZONTAL, VERTICAL, ModelAdmin, TabularInline,
)
from django.contrib.admin.sites import AdminSite
from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
from django.core.checks import Error
from django.forms.models import BaseModelFormSet
from django.forms.widgets import Select
from django.test import SimpleTestCase, TestCase
from django.utils import six

from .models import (
    Band, Concert, ValidationTestInlineModel, ValidationTestModel,
)

class MockRequest(object):
    pass

class MockSuperUser(object):
    def has_perm(self, perm):
        return True

request = MockRequest()
request.user = MockSuperUser()

class ModelAdminTests(TestCase):

    def setUp(self):
        self.band = Band.objects.create(
            name='The Doors',
            bio='',
            sign_date=date(1965, 1, 1),
        )
        self.site = AdminSite()

    # form/fields/fieldsets interaction ##############################

    def test_default_fields(self):
        ma = ModelAdmin(Band, self.site)

        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'bio', 'sign_date'])
        self.assertEqual(list(ma.get_fields(request)), ['name', 'bio', 'sign_date'])
        self.assertEqual(list(ma.get_fields(request, self.band)), ['name', 'bio', 'sign_date'])

    def test_default_fieldsets(self):
        # fieldsets_add and fieldsets_change should return a special data structure that
        # is used in the templates. They should generate the "right thing" whether we
        # have specified a custom form, the fields argument, or nothing at all.
        #
        # Here's the default case. There are no custom form_add/form_change methods,
        # no fields argument, and no fieldsets argument.
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(ma.get_fieldsets(request), [(None, {'fields': ['name', 'bio', 'sign_date']})])

        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {'fields': ['name', 'bio', 'sign_date']})])

    def test_get_fieldsets(self):
        # Test that get_fieldsets is called when figuring out form fields.
        # Refs #18681.

        class BandAdmin(ModelAdmin):
            def get_fieldsets(self, request, obj=None):
                return [(None, {'fields': ['name', 'bio']})]

        ma = BandAdmin(Band, self.site)
        form = ma.get_form(None)
        self.assertEqual(form._meta.fields, ['name', 'bio'])

        class InlineBandAdmin(TabularInline):
            model = Concert
            fk_name = 'main_band'
            can_delete = False

            def get_fieldsets(self, request, obj=None):
                return [(None, {'fields': ['day', 'transport']})]

        ma = InlineBandAdmin(Band, self.site)
        form = ma.get_formset(None).form
        self.assertEqual(form._meta.fields, ['day', 'transport'])

    def test_lookup_allowed_allows_nonexistent_lookup(self):
        """
        Ensure that a lookup_allowed allows a parameter
        whose field lookup doesn't exist.
        Refs #21129.
        """
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)
        self.assertTrue(ma.lookup_allowed('name__nonexistent', 'test_value'))

    def test_field_arguments(self):
        # If we specify the fields argument, fieldsets_add and fieldsets_change should
        # just stick the fields into a formsets structure and return it.
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)

        self.assertEqual(list(ma.get_fields(request)), ['name'])
        self.assertEqual(list(ma.get_fields(request, self.band)), ['name'])
        self.assertEqual(ma.get_fieldsets(request), [(None, {'fields': ['name']})])
        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {'fields': ['name']})])

    def test_field_arguments_restricted_on_form(self):
        # If we specify fields or fieldsets, it should exclude fields on the Form class
        # to the fields specified. This may cause errors to be raised in the db layer if
        # required model fields aren't in fields/fieldsets, but that's preferable to
        # ghost errors where you have a field in your Form class that isn't being
        # displayed because you forgot to add it to fields/fieldsets

        # Using `fields`.
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields), ['name'])

        # Using `fieldsets`.
        class BandAdmin(ModelAdmin):
            fieldsets = [(None, {'fields': ['name']})]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields), ['name'])

        # Using `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ['bio']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'sign_date'])

        # You can also pass a tuple to `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ('bio',)

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'sign_date'])

        # Using `fields` and `exclude`.
        class BandAdmin(ModelAdmin):
            fields = ['name', 'bio']
            exclude = ['bio']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])

    def test_custom_form_meta_exclude_with_readonly(self):
        """
        Ensure that the custom ModelForm's `Meta.exclude` is respected when
        used in conjunction with `ModelAdmin.readonly_fields` and when no
        `ModelAdmin.exclude` is defined.
        Refs #14496.
        """
        # First, with `ModelAdmin` -----------------------

        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['bio']

        class BandAdmin(ModelAdmin):
            readonly_fields = ['name']
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['sign_date'])

        # Then, with `InlineModelAdmin`  -----------------

        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            readonly_fields = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [
                ConcertInline
            ]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'opening_band', 'id', 'DELETE'])

    def test_custom_formfield_override_readonly(self):
        class AdminBandForm(forms.ModelForm):
            name = forms.CharField()

            class Meta:
                exclude = tuple()
                model = Band

        class BandAdmin(ModelAdmin):
            form = AdminBandForm
            readonly_fields = ['name']

        ma = BandAdmin(Band, self.site)

        # `name` shouldn't appear in base_fields because it's part of
        # readonly_fields.
        self.assertEqual(
            list(ma.get_form(request).base_fields),
            ['bio', 'sign_date']
        )
        # But it should appear in get_fields()/fieldsets() so it can be
        # displayed as read-only.
        self.assertEqual(
            list(ma.get_fields(request)),
            ['bio', 'sign_date', 'name']
        )
        self.assertEqual(
            list(ma.get_fieldsets(request)),
            [(None, {'fields': ['bio', 'sign_date', 'name']})]
        )

    def test_custom_form_meta_exclude(self):
        """
        Ensure that the custom ModelForm's `Meta.exclude` is overridden if
        `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined.
        Refs #14496.
        """
        # First, with `ModelAdmin` -----------------------

        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['bio']

        class BandAdmin(ModelAdmin):
            exclude = ['name']
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['bio', 'sign_date'])

        # Then, with `InlineModelAdmin`  -----------------

        class AdminConcertForm(forms.ModelForm):

            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            exclude = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [
                ConcertInline
            ]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'opening_band', 'day', 'id', 'DELETE']
        )

    def test_custom_form_validation(self):
        # If we specify a form, it should use it allowing custom validation to work
        # properly. This won't, however, break any of the admin widgets or media.

        class AdminBandForm(forms.ModelForm):
            delete = forms.BooleanField()

        class BandAdmin(ModelAdmin):
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'bio', 'sign_date', 'delete'])
        self.assertEqual(type(ma.get_form(request).base_fields['sign_date'].widget), AdminDateWidget)

    def test_form_exclude_kwarg_override(self):
        """
        Ensure that the `exclude` kwarg passed to `ModelAdmin.get_form()`
        overrides all other declarations. Refs #8999.
        """

        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['name']

        class BandAdmin(ModelAdmin):
            exclude = ['sign_date']
            form = AdminBandForm

            def get_form(self, request, obj=None, **kwargs):
                kwargs['exclude'] = ['bio']
                return super(BandAdmin, self).get_form(request, obj, **kwargs)

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'sign_date'])

    def test_formset_exclude_kwarg_override(self):
        """
        Ensure that the `exclude` kwarg passed to `InlineModelAdmin.get_formset()`
        overrides all other declarations. Refs #8999.
        """

        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            exclude = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_formset(self, request, obj=None, **kwargs):
                kwargs['exclude'] = ['opening_band']
                return super(ConcertInline, self).get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [
                ConcertInline
            ]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE'])

    def test_queryset_override(self):
        # If we need to override the queryset of a ModelChoiceField in our custom form
        # make sure that RelatedFieldWidgetWrapper doesn't mess that up.

        band2 = Band(name='The Beatles', bio='', sign_date=date(1962, 1, 1))
        band2.save()

        class ConcertAdmin(ModelAdmin):
            pass
        ma = ConcertAdmin(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]),
            '
'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected="selected">---------</option>'
            '<option value="%d">The Beatles</option>'
            '<option value="%d">The Doors</option>'
            '</select>
' % (band2.id, self.band.id)
        )

        class AdminConcertForm(forms.ModelForm):
            def __init__(self, *args, **kwargs):
                super(AdminConcertForm, self).__init__(*args, **kwargs)
                self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')

        class ConcertAdminWithForm(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdminWithForm(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]),
            '
'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected="selected">---------</option>'
            '<option value="%d">The Doors</option>'
            '</select>
' % self.band.id
        )

    def test_regression_for_ticket_15820(self):
        """
        Ensure that `obj` is passed from `InlineModelAdmin.get_fieldsets()` to
        `InlineModelAdmin.get_formset()`.
        """
        class CustomConcertForm(forms.ModelForm):

            class Meta:
                model = Concert
                fields = ['day']

        class ConcertInline(TabularInline):
            model = Concert
            fk_name = 'main_band'

            def get_formset(self, request, obj=None, **kwargs):
                if obj:
                    kwargs['form'] = CustomConcertForm
                return super(ConcertInline, self).get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [
                ConcertInline
            ]

        Concert.objects.create(main_band=self.band, opening_band=self.band, day=1)
        ma = BandAdmin(Band, self.site)
        inline_instances = ma.get_inline_instances(request)
        fieldsets = list(inline_instances[0].get_fieldsets(request))
        self.assertEqual(fieldsets[0][1]['fields'], ['main_band', 'opening_band', 'day', 'transport'])
        fieldsets = list(inline_instances[0].get_fieldsets(request, inline_instances[0].model))
        self.assertEqual(fieldsets[0][1]['fields'], ['day'])

    # radio_fields behavior ###########################################

    def test_default_foreign_key_widget(self):
        # First, without any radio_fields specified, the widgets for ForeignKey
        # and fields with choices specified ought to be a basic Select widget.
        # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
        # they need to be handled properly when type checking. For Select fields, all of
        # the choices lists have a first entry of dashes.

        cma = ModelAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['main_band'].widget.choices),
            [('', '---------'), (self.band.id, 'The Doors')])

        self.assertEqual(type(cmafa.base_fields['opening_band'].widget.widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['opening_band'].widget.choices),
            [('', '---------'), (self.band.id, 'The Doors')]
        )
        self.assertEqual(type(cmafa.base_fields['day'].widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['day'].widget.choices),
            [('', '---------'), (1, 'Fri'), (2, 'Sat')]
        )
        self.assertEqual(type(cmafa.base_fields['transport'].widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['transport'].widget.choices),
            [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])

    def test_foreign_key_as_radio_field(self):
        # Now specify all the fields as radio_fields.  Widgets should now be
        # RadioSelect, and the choices list should have a first entry of 'None' if
        # blank=True for the model field.  Finally, the widget should have the
        # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.

        class ConcertAdmin(ModelAdmin):
            radio_fields = {
                'main_band': HORIZONTAL,
                'opening_band': VERTICAL,
                'day': VERTICAL,
                'transport': HORIZONTAL,
            }

        cma = ConcertAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['main_band'].widget.attrs, {'class': 'radiolist inline'})
        self.assertEqual(
            list(cmafa.base_fields['main_band'].widget.choices),
            [(self.band.id, 'The Doors')]
        )

        self.assertEqual(type(cmafa.base_fields['opening_band'].widget.widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs, {'class': 'radiolist'})
        self.assertEqual(
            list(cmafa.base_fields['opening_band'].widget.choices),
            [('', 'None'), (self.band.id, 'The Doors')]
        )
        self.assertEqual(type(cmafa.base_fields['day'].widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['day'].widget.attrs, {'class': 'radiolist'})
        self.assertEqual(list(cmafa.base_fields['day'].widget.choices), [(1, 'Fri'), (2, 'Sat')])

        self.assertEqual(type(cmafa.base_fields['transport'].widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['transport'].widget.attrs, {'class': 'radiolist inline'})
        self.assertEqual(
            list(cmafa.base_fields['transport'].widget.choices),
            [('', 'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
        )

        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ('transport',)

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['main_band', 'opening_band', 'day'])

        class AdminConcertForm(forms.ModelForm):
            extra = forms.CharField()

            class Meta:
                model = Concert
                fields = ['extra', 'transport']

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['extra', 'transport'])

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            model = Concert
            fk_name = 'main_band'
            can_delete = True

        class BandAdmin(ModelAdmin):
            inlines = [
                ConcertInline
            ]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['extra', 'transport', 'id', 'DELETE', 'main_band']
        )

class CheckTestCase(SimpleTestCase):

    def assertIsInvalid(self, model_admin, model, msg, id=None, hint=None, invalid_obj=None):
        invalid_obj = invalid_obj or model_admin
        admin_obj = model_admin(model, AdminSite())
        errors = admin_obj.check()
        expected = [
            Error(
                msg,
                hint=hint,
                obj=invalid_obj,
                id=id,
            )
        ]
        self.assertEqual(errors, expected)

    def assertIsInvalidRegexp(self, model_admin, model, msg, id=None, hint=None, invalid_obj=None):
        """
        Same as assertIsInvalid but treats the given msg as a regexp.
        """
        invalid_obj = invalid_obj or model_admin
        admin_obj = model_admin(model, AdminSite())
        errors = admin_obj.check()
        self.assertEqual(len(errors), 1)
        error = errors[0]
        self.assertEqual(error.hint, hint)
        self.assertEqual(error.obj, invalid_obj)
        self.assertEqual(error.id, id)
        six.assertRegex(self, error.msg, msg)

    def assertIsValid(self, model_admin, model):
        admin_obj = model_admin(model, AdminSite())
        errors = admin_obj.check()
        expected = []
        self.assertEqual(errors, expected)

class RawIdCheckTests(CheckTestCase):

    def test_not_iterable(self):
        class ValidationTestModelAdmin(ModelAdmin):
            raw_id_fields = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'raw_id_fields' must be a list or tuple.",
            'admin.E001')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            raw_id_fields = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'raw_id_fields[0]' refers to 'non_existent_field', "
             "which is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E002')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            raw_id_fields = ('name',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'raw_id_fields[0]' must be a foreign key or a many-to-many field.",
            'admin.E003')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            raw_id_fields = ('users',)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class FieldsetsCheckTests(CheckTestCase):

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = (("General", {'fields': ('name',)}),)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

    def test_not_iterable(self):

        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fieldsets' must be a list or tuple.",
            'admin.E007')

    def test_non_iterable_item(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = ({},)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fieldsets[0]' must be a list or tuple.",
            'admin.E008')

    def test_item_not_a_pair(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = ((),)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fieldsets[0]' must be of length 2.",
            'admin.E009')

    def test_second_element_of_item_not_a_dict(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = (("General", ()),)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fieldsets[0][1]' must be a dictionary.",
            'admin.E010')

    def test_missing_fields_key(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = (("General", {}),)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fieldsets[0][1]' must contain the key 'fields'.",
            'admin.E011')

        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = (("General", {'fields': ('name',)}),)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

    def test_specified_both_fields_and_fieldsets(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = (("General", {'fields': ('name',)}),)
            fields = ['name']

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "Both 'fieldsets' and 'fields' are specified.",
            'admin.E005')

    def test_duplicate_fields(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fieldsets = [(None, {'fields': ['name', 'name']})]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "There are duplicate field(s) in 'fieldsets[0][1]'.",
            'admin.E012')

    def test_fieldsets_with_custom_form_validation(self):
        class BandAdmin(ModelAdmin):
            fieldsets = (
                ('Band', {
                    'fields': ('name',)
                }),
            )

        self.assertIsValid(BandAdmin, Band)

class FieldsCheckTests(CheckTestCase):

    def test_duplicate_fields_in_fields(self):
        class ValidationTestModelAdmin(ModelAdmin):
            fields = ['name', 'name']

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fields' contains duplicate field(s).",
            'admin.E006')

    def test_inline(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            fields = 10

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'fields' must be a list or tuple.",
            'admin.E004',
            invalid_obj=ValidationTestInline)

class FormCheckTests(CheckTestCase):

    def test_invalid_type(self):
        class FakeForm(object):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            form = FakeForm

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'form' must inherit from 'BaseModelForm'.",
            'admin.E016')

    def test_fieldsets_with_custom_form_validation(self):

        class BandAdmin(ModelAdmin):
            fieldsets = (
                ('Band', {
                    'fields': ('name',)
                }),
            )

        self.assertIsValid(BandAdmin, Band)

    def test_valid_case(self):
        class AdminBandForm(forms.ModelForm):
            delete = forms.BooleanField()

        class BandAdmin(ModelAdmin):
            form = AdminBandForm

            fieldsets = (
                ('Band', {
                    'fields': ('name', 'bio', 'sign_date', 'delete')
                }),
            )

        self.assertIsValid(BandAdmin, Band)

class FilterVerticalCheckTests(CheckTestCase):

    def test_not_iterable(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_vertical = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'filter_vertical' must be a list or tuple.",
            'admin.E017')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_vertical = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'filter_vertical[0]' refers to 'non_existent_field', "
             "which is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E019')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_vertical = ('name',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'filter_vertical[0]' must be a many-to-many field.",
            'admin.E020')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_vertical = ("users",)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class FilterHorizontalCheckTests(CheckTestCase):

    def test_not_iterable(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_horizontal = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'filter_horizontal' must be a list or tuple.",
            'admin.E018')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_horizontal = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'filter_horizontal[0]' refers to 'non_existent_field', "
             "which is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E019')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_horizontal = ('name',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'filter_horizontal[0]' must be a many-to-many field.",
            'admin.E020')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            filter_horizontal = ("users",)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class RadioFieldsCheckTests(CheckTestCase):

    def test_not_dictionary(self):

        class ValidationTestModelAdmin(ModelAdmin):
            radio_fields = ()

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'radio_fields' must be a dictionary.",
            'admin.E021')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            radio_fields = {'non_existent_field': VERTICAL}

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'radio_fields' refers to 'non_existent_field', "
             "which is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E022')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            radio_fields = {'name': VERTICAL}

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'radio_fields' refers to 'name', which is not an instance "
             "of ForeignKey, and does not have a 'choices' definition."),
            'admin.E023')

    def test_invalid_value(self):
        class ValidationTestModelAdmin(ModelAdmin):
            radio_fields = {"state": None}

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'radio_fields[\"state\"]' must be either admin.HORIZONTAL or admin.VERTICAL.",
            'admin.E024')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            radio_fields = {"state": VERTICAL}

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class PrepopulatedFieldsCheckTests(CheckTestCase):

    def test_not_dictionary(self):

        class ValidationTestModelAdmin(ModelAdmin):
            prepopulated_fields = ()

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'prepopulated_fields' must be a dictionary.",
            'admin.E026')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            prepopulated_fields = {'non_existent_field': ("slug",)}

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'prepopulated_fields' refers to 'non_existent_field', "
             "which is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E027')

    def test_missing_field_again(self):
        class ValidationTestModelAdmin(ModelAdmin):
            prepopulated_fields = {"slug": ('non_existent_field',)}

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'prepopulated_fields[\"slug\"][0]' refers to 'non_existent_field', "
             "which is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E030')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            prepopulated_fields = {"users": ('name',)}

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'prepopulated_fields' refers to 'users', which must not be "
             "a DateTimeField, a foreign key, or a many-to-many field."),
            'admin.E028')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            prepopulated_fields = {"slug": ('name',)}

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListDisplayTests(CheckTestCase):

    def test_not_iterable(self):

        class ValidationTestModelAdmin(ModelAdmin):
            list_display = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_display' must be a list or tuple.",
            'admin.E107')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_display = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'list_display[0]' refers to 'non_existent_field', which is not a callable, an attribute "
             "of 'ValidationTestModelAdmin', or an attribute or method on 'modeladmin.ValidationTestModel'."),
            'admin.E108')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_display = ('users',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_display[0]' must not be a many-to-many field.",
            'admin.E109')

    def test_valid_case(self):
        def a_callable(obj):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            def a_method(self, obj):
                pass
            list_display = ('name', 'decade_published_in', 'a_method', a_callable)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListDisplayLinksCheckTests(CheckTestCase):

    def test_not_iterable(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_display_links = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_display_links' must be a list, a tuple, or None.",
            'admin.E110')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_display_links = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel, (
                "The value of 'list_display_links[0]' refers to "
                "'non_existent_field', which is not defined in 'list_display'."
            ), 'admin.E111'
        )

    def test_missing_in_list_display(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_display_links = ('name',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_display_links[0]' refers to 'name', which is not defined in 'list_display'.",
            'admin.E111')

    def test_valid_case(self):
        def a_callable(obj):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            def a_method(self, obj):
                pass
            list_display = ('name', 'decade_published_in', 'a_method', a_callable)
            list_display_links = ('name', 'decade_published_in', 'a_method', a_callable)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

    def test_None_is_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_display_links = None

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListFilterTests(CheckTestCase):

    def test_list_filter_validation(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_filter' must be a list or tuple.",
            'admin.E112')

    def test_missing_field(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_filter[0]' refers to 'non_existent_field', which does not refer to a Field.",
            'admin.E116')

    def test_not_filter(self):
        class RandomClass(object):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = (RandomClass,)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_filter[0]' must inherit from 'ListFilter'.",
            'admin.E113')

    def test_not_filter_again(self):
        class RandomClass(object):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = (('is_active', RandomClass),)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_filter[0][1]' must inherit from 'FieldListFilter'.",
            'admin.E115')

    def test_not_filter_again_again(self):
        class AwesomeFilter(SimpleListFilter):
            def get_title(self):
                return 'awesomeness'

            def get_choices(self, request):
                return (('bit', 'A bit awesome'), ('very', 'Very awesome'), )

            def get_queryset(self, cl, qs):
                return qs

        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = (('is_active', AwesomeFilter),)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_filter[0][1]' must inherit from 'FieldListFilter'.",
            'admin.E115')

    def test_not_associated_with_field_name(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = (BooleanFieldListFilter,)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_filter[0]' must not inherit from 'FieldListFilter'.",
            'admin.E114')

    def test_valid_case(self):
        class AwesomeFilter(SimpleListFilter):
            def get_title(self):
                return 'awesomeness'

            def get_choices(self, request):
                return (('bit', 'A bit awesome'), ('very', 'Very awesome'), )

            def get_queryset(self, cl, qs):
                return qs

        class ValidationTestModelAdmin(ModelAdmin):
            list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter), 'no')

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListPerPageCheckTests(CheckTestCase):

    def test_not_integer(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_per_page = 'hello'

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_per_page' must be an integer.",
            'admin.E118')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_per_page = 100

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListMaxShowAllCheckTests(CheckTestCase):

    def test_not_integer(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_max_show_all = 'hello'

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_max_show_all' must be an integer.",
            'admin.E119')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_max_show_all = 200

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class SearchFieldsCheckTests(CheckTestCase):

    def test_not_iterable(self):

        class ValidationTestModelAdmin(ModelAdmin):
            search_fields = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'search_fields' must be a list or tuple.",
            'admin.E126')

class DateHierarchyCheckTests(CheckTestCase):

    def test_missing_field(self):

        class ValidationTestModelAdmin(ModelAdmin):
            date_hierarchy = 'non_existent_field'

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            ("The value of 'date_hierarchy' refers to 'non_existent_field', which "
             "is not an attribute of 'modeladmin.ValidationTestModel'."),
            'admin.E127')

    def test_invalid_field_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            date_hierarchy = 'name'

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'date_hierarchy' must be a DateField or DateTimeField.",
            'admin.E128')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            date_hierarchy = 'pub_date'

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class OrderingCheckTests(CheckTestCase):

    def test_not_iterable(self):
        class ValidationTestModelAdmin(ModelAdmin):
            ordering = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'ordering' must be a list or tuple.",
            'admin.E031'
        )

        class ValidationTestModelAdmin(ModelAdmin):
            ordering = ('non_existent_field',)

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'ordering[0]' refers to 'non_existent_field', "
            "which is not an attribute of 'modeladmin.ValidationTestModel'.",
            'admin.E033'
        )

    def test_random_marker_not_alone(self):
        class ValidationTestModelAdmin(ModelAdmin):
            ordering = ('?', 'name')

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'ordering' has the random ordering marker '?', but contains "
            "other fields as well.",
            'admin.E032',
            hint='Either remove the "?", or remove the other fields.'
        )

    def test_valid_random_marker_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            ordering = ('?',)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

    def test_valid_complex_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            ordering = ('band__name',)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            ordering = ('name',)

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListSelectRelatedCheckTests(CheckTestCase):

    def test_invalid_type(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_select_related = 1

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'list_select_related' must be a boolean, tuple or list.",
            'admin.E117')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            list_select_related = False

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class SaveAsCheckTests(CheckTestCase):

    def test_not_boolean(self):
        class ValidationTestModelAdmin(ModelAdmin):
            save_as = 1

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'save_as' must be a boolean.",
            'admin.E101')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            save_as = True

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class SaveOnTopCheckTests(CheckTestCase):

    def test_not_boolean(self):
        class ValidationTestModelAdmin(ModelAdmin):
            save_on_top = 1

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'save_on_top' must be a boolean.",
            'admin.E102')

    def test_valid_case(self):
        class ValidationTestModelAdmin(ModelAdmin):
            save_on_top = True

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class InlinesCheckTests(CheckTestCase):

    def test_not_iterable(self):
        class ValidationTestModelAdmin(ModelAdmin):
            inlines = 10

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'inlines' must be a list or tuple.",
            'admin.E103')

    def test_not_model_admin(self):
        class ValidationTestInline(object):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalidRegexp(
            ValidationTestModelAdmin, ValidationTestModel,
            r"'.*\.ValidationTestInline' must inherit from 'BaseModelAdmin'\.",
            'admin.E104')

    def test_missing_model_field(self):
        class ValidationTestInline(TabularInline):
            pass

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalidRegexp(
            ValidationTestModelAdmin, ValidationTestModel,
            r"'.*\.ValidationTestInline' must have a 'model' attribute\.",
            'admin.E105')

    def test_invalid_model_type(self):
        """ Test if `model` attribute on inline model admin is a models.Model.
        """

        class SomethingBad(object):
            pass

        class ValidationTestInline(TabularInline):
            model = SomethingBad

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalidRegexp(
            ValidationTestModelAdmin, ValidationTestModel,
            r"The value of '.*\.ValidationTestInline.model' must be a Model\.",
            'admin.E106')

    def test_valid_case(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class FkNameCheckTests(CheckTestCase):

    def test_missing_field(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            fk_name = 'non_existent_field'

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "'modeladmin.ValidationTestInlineModel' has no field named 'non_existent_field'.",
            'admin.E202',
            invalid_obj=ValidationTestInline)

    def test_valid_case(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            fk_name = "parent"

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ExtraCheckTests(CheckTestCase):

    def test_not_integer(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            extra = "hello"

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'extra' must be an integer.",
            'admin.E203',
            invalid_obj=ValidationTestInline)

    def test_valid_case(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            extra = 2

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class MaxNumCheckTests(CheckTestCase):

    def test_not_integer(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            max_num = "hello"

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'max_num' must be an integer.",
            'admin.E204',
            invalid_obj=ValidationTestInline)

    def test_valid_case(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            max_num = 2

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class MinNumCheckTests(CheckTestCase):

    def test_not_integer(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            min_num = "hello"

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'min_num' must be an integer.",
            'admin.E205',
            invalid_obj=ValidationTestInline)

    def test_valid_case(self):
        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            min_num = 2

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class FormsetCheckTests(CheckTestCase):

    def test_invalid_type(self):
        class FakeFormSet(object):
            pass

        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            formset = FakeFormSet

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsInvalid(
            ValidationTestModelAdmin, ValidationTestModel,
            "The value of 'formset' must inherit from 'BaseModelFormSet'.",
            'admin.E206',
            invalid_obj=ValidationTestInline)

    def test_valid_case(self):
        class RealModelFormSet(BaseModelFormSet):
            pass

        class ValidationTestInline(TabularInline):
            model = ValidationTestInlineModel
            formset = RealModelFormSet

        class ValidationTestModelAdmin(ModelAdmin):
            inlines = [ValidationTestInline]

        self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)

class ListDisplayEditableTests(CheckTestCase):
    def test_list_display_links_is_none(self):
        """
        list_display and list_editable can contain the same values
        when list_display_links is None
        """
        class ProductAdmin(ModelAdmin):
            list_display = ['name', 'slug', 'pub_date']
            list_editable = list_display
            list_display_links = None
        self.assertIsValid(ProductAdmin, ValidationTestModel)

    def test_list_display_first_item_same_as_list_editable_first_item(self):
        """
        The first item in list_display can be the same as the first in
        list_editable.
        """
        class ProductAdmin(ModelAdmin):
            list_display = ['name', 'slug', 'pub_date']
            list_editable = ['name', 'slug']
            list_display_links = ['pub_date']
        self.assertIsValid(ProductAdmin, ValidationTestModel)

    def test_list_display_first_item_in_list_editable(self):
        """
        The first item in list_display can be in list_editable as long as
        list_display_links is defined.
        """
        class ProductAdmin(ModelAdmin):
            list_display = ['name', 'slug', 'pub_date']
            list_editable = ['slug', 'name']
            list_display_links = ['pub_date']
        self.assertIsValid(ProductAdmin, ValidationTestModel)

    def test_list_display_first_item_same_as_list_editable_no_list_display_links(self):
        """
        The first item in list_display cannot be the same as the first item
        in list_editable if list_display_links is not defined.
        """
        class ProductAdmin(ModelAdmin):
            list_display = ['name']
            list_editable = ['name']
        self.assertIsInvalid(
            ProductAdmin, ValidationTestModel,
            "The value of 'list_editable[0]' refers to the first field "
            "in 'list_display' ('name'), which cannot be used unless "
            "'list_display_links' is set.",
            id='admin.E124',
        )

    def test_list_display_first_item_in_list_editable_no_list_display_links(self):
        """
        The first item in list_display cannot be in list_editable if
        list_display_links isn't defined.
        """
        class ProductAdmin(ModelAdmin):
            list_display = ['name', 'slug', 'pub_date']
            list_editable = ['slug', 'name']
        self.assertIsInvalid(
            ProductAdmin, ValidationTestModel,
            "The value of 'list_editable[1]' refers to the first field "
            "in 'list_display' ('name'), which cannot be used unless "
            "'list_display_links' is set.",
            id='admin.E124',
        )

class ModelAdminPermissionTests(SimpleTestCase):

    class MockUser(object):
        def has_module_perms(self, app_label):
            if app_label == "modeladmin":
                return True
            return False

    class MockAddUser(MockUser):
        def has_perm(self, perm):
            if perm == "modeladmin.add_band":
                return True
            return False

    class MockChangeUser(MockUser):
        def has_perm(self, perm):
            if perm == "modeladmin.change_band":
                return True
            return False

    class MockDeleteUser(MockUser):
        def has_perm(self, perm):
            if perm == "modeladmin.delete_band":
                return True
            return False

    def test_has_add_permission(self):
        """
        Ensure that has_add_permission returns True for users who can add
        objects and False for users who can't.
        """
        ma = ModelAdmin(Band, AdminSite())
        request = MockRequest()
        request.user = self.MockAddUser()
        self.assertTrue(ma.has_add_permission(request))
        request.user = self.MockChangeUser()
        self.assertFalse(ma.has_add_permission(request))
        request.user = self.MockDeleteUser()
        self.assertFalse(ma.has_add_permission(request))

    def test_has_change_permission(self):
        """
        Ensure that has_change_permission returns True for users who can edit
        objects and False for users who can't.
        """
        ma = ModelAdmin(Band, AdminSite())
        request = MockRequest()
        request.user = self.MockAddUser()
        self.assertFalse(ma.has_change_permission(request))
        request.user = self.MockChangeUser()
        self.assertTrue(ma.has_change_permission(request))
        request.user = self.MockDeleteUser()
        self.assertFalse(ma.has_change_permission(request))

    def test_has_delete_permission(self):
        """
        Ensure that has_delete_permission returns True for users who can delete
        objects and False for users who can't.
        """
        ma = ModelAdmin(Band, AdminSite())
        request = MockRequest()
        request.user = self.MockAddUser()
        self.assertFalse(ma.has_delete_permission(request))
        request.user = self.MockChangeUser()
        self.assertFalse(ma.has_delete_permission(request))
        request.user = self.MockDeleteUser()
        self.assertTrue(ma.has_delete_permission(request))

    def test_has_module_permission(self):
        """
        Ensure that has_module_permission returns True for users who have any
        permission for the module and False for users who don't.
        """
        ma = ModelAdmin(Band, AdminSite())
        request = MockRequest()
        request.user = self.MockAddUser()
        self.assertTrue(ma.has_module_permission(request))
        request.user = self.MockChangeUser()
        self.assertTrue(ma.has_module_permission(request))
        request.user = self.MockDeleteUser()
        self.assertTrue(ma.has_module_permission(request))

        original_app_label = ma.opts.app_label
        ma.opts.app_label = 'anotherapp'
        try:
            request.user = self.MockAddUser()
            self.assertFalse(ma.has_module_permission(request))
            request.user = self.MockChangeUser()
            self.assertFalse(ma.has_module_permission(request))
            request.user = self.MockDeleteUser()
            self.assertFalse(ma.has_module_permission(request))
        finally:
            ma.opts.app_label = original_app_label


text:
package com.slothpetrochemical.bridgeprob;

import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.List;

/**
 * Class providing a compact representation of a single state in the
 * Bridge/Flashlight problem.
 */
public final class BridgeProblemSignature
        implements Comparable<BridgeProblemSignature>,
                   BridgeProblemStateDescriptor
{
    public static final String BRIDGE_DELIMITER = " || ";
    public static final String NAME_DELIMITER = ",";
    public static final String FLASHLIGHT = "(*)";

    private final String signature;

    BridgeProblemSignature(final BridgeProblemStateDescriptor descriptor)
    {
        if (descriptor instanceof BridgeProblemSignature)
        {
            BridgeProblemSignature otherSig =
                    (BridgeProblemSignature) descriptor;
            this.signature = otherSig.signature;
        }
        else
        {
            this.signature =
                    BridgeProblemSignature.composeSignature(descriptor);
        }
    }

    @Override
    public int compareTo(final BridgeProblemSignature o)
    {
        return this.signature.compareTo(o.signature);
    }

    // package private
    static String composeSignature(
            final BridgeProblemStateDescriptor descriptor)
    {
        StringBuilder sb = new StringBuilder();
        boolean flashlightOnLeft = descriptor.isFlashlightOnLeft();
        sb.append(StringUtils.join(descriptor.getNamesOnLeft(),
                                   NAME_DELIMITER));
        if (flashlightOnLeft)
        {
            sb.append(FLASHLIGHT);
        }
        sb.append(BRIDGE_DELIMITER);
        sb.append(StringUtils.join(descriptor.getNamesOnRight(),
                                   NAME_DELIMITER));
        if (!flashlightOnLeft)
        {
            sb.append(FLASHLIGHT);
        }
        return sb.toString();
    }

    private CharSequence getLeftSegment()
    {
        int bridgeIdx = this.signature.indexOf(BRIDGE_DELIMITER);
        return this.signature.subSequence(0, bridgeIdx);
    }

    private CharSequence getRightSegment()
    {
        int bridgeIdx = this.signature.indexOf(BRIDGE_DELIMITER);
        return this.signature.subSequence(bridgeIdx + BRIDGE_DELIMITER.length(),
                                          this.signature.length());
    }

    private static CharSequence stripFlashlight(final CharSequence seq)
    {
        int lastIdx = StringUtils.lastIndexOf(seq, FLASHLIGHT);
        return lastIdx >= 0 ? seq.subSequence(0, lastIdx) : seq;
    }

    @Override
    public List<String> getNamesOnLeft()
    {
        String names = stripFlashlight(this.getLeftSegment()).toString();
        return Arrays.asList(StringUtils.splitByWholeSeparator(names,
                                                               NAME_DELIMITER));
    }

    @Override
    public List<String> getNamesOnRight()
    {
        String names = stripFlashlight(this.getRightSegment()).toString();
        return Arrays.asList(StringUtils.splitByWholeSeparator(names,
                                                               NAME_DELIMITER));
    }

    @Override
    public boolean isFlashlightOnLeft()
    {
        CharSequence leftSegment = this.getLeftSegment();
        return StringUtils.lastIndexOf(leftSegment, FLASHLIGHT) > -1;
    }

    //CHECKSTYLE:OFF
    @Override
    public boolean equals(final Object o)
    {
        if (this == o)
        {
            return true;
        }
        if (o == null || getClass() != o.getClass())
        {
            return false;
        }

        BridgeProblemSignature that = (BridgeProblemSignature) o;

        if (!signature.equals(that.signature))
        {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode()
    {
        int result = signature.hashCode();
        return result;
    }
    //CHECKSTYLE:ON

    @Override
    public String toString()
    {
        return this.signature;
    }
}